数据库迁移完成、服务启动前是执行seed的最稳妥时机;需在迁移后立即调用,使用事务确保原子性,避免并发干扰和重复插入,并通过cli命令或显式flag控制环境隔离。

Go迁移后执行Seed的典型时机在哪
数据库迁移完成、服务启动前,是插入初始数据最稳妥的窗口。早于迁移会报表不存在,晚于启动可能被并发请求干扰。很多项目误把Seed塞进main()开头或HTTP handler里,结果迁移还没跑完就去写数据,直接触发ERROR: relation "users" does not exist。
- 使用
goose up或migrate up等工具执行迁移后,立刻调用Seed函数,不要依赖“启动时自动检测迁移状态” - 如果用
sqlc或ent生成了CRUD代码,Seed应基于生成的Queries或Client操作,而非裸SQL——避免字段名变更后Seed silently 失效 - 不在
init()里做Seed:无法传入已初始化的*sql.DB,且测试时难以跳过
用Go写Seed函数要注意哪些参数和事务边界
Seed不是“插几条INSERT”,它需要明确控制事务、幂等性、错误传播。常见错误是每条记录单独Exec,导致中途失败后残留脏数据。
必须用
db.Begin()开启事务,所有插入在同一个*sql.Tx中完成,最后统一Commit()或Rollback()每条INSERT前加
ON CONFLICT DO NOTHING(PostgreSQL)或INSERT IGNORE(MySQL),防止重复运行Seed崩掉Seed函数接收
*sql.DB而非连接字符串,方便测试时注入sqlite.Memory或mock DB
Golang Spf13 Viper下载Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
示例片段:
func SeedUsers(db *sql.DB) error { tx, err := db.Begin() if err != nil { return err } defer tx.Rollback() <p>q := New(tx) for <em>, u := range initialUsers { </em>, err := q.CreateUser(context.Background(), CreateUserParams{ Name: u.Name, Email: u.Email, }) if err != nil { return err } } return tx.Commit() }</p>
如何让Seed只在开发/测试环境运行,不进生产
线上跑Seed轻则覆盖配置项,重则清空真实用户权限表。必须从执行入口层隔离,不能靠“注释掉”或if os.Getenv("ENV") == "dev"这种易漏逻辑。
- 在CLI命令中显式提供
seed子命令,例如./myapp migrate up && ./myapp seed,生产部署脚本里直接不调用 - 若集成进Web服务启动流程,用
-seedflag控制:./myapp -migrate -seed,flag默认false - 环境变量可作为兜底判断,但必须校验值是否为精确的
"dev"或"test",避免staging误触发 - 不要读取
GO_ENV或APP_ENV这类模糊变量——它们常被CI/CD覆盖,行为不可控
用GORM或Ent做Seed时容易忽略的兼容性点
ORM封装了SQL,但也藏了陷阱:GORM的CreateInBatches默认不走事务;Ent的CreateMany在PostgreSQL下遇到唯一约束会panic,而非返回ErrConstraint。
- GORM:务必手动
db.Session(&gorm.Session{AllowGlobalUpdate: false}).Transaction(...),禁用全局更新,强制事务 - Ent:用
client.User.CreateBulk(...).OnConflict(...).DoNothing().Exec(ctx),而不是CreateMany直插 - 所有Seed数据结构必须用
time.Now().UTC()设时间戳,避免本地时区导致测试失败(尤其SQLite内存DB) - 如果Seed含外键关联(如Role → User),确保插入顺序严格按依赖拓扑,或关掉外键检查(仅限SQLite测试)
Seed这事,核心不在“怎么插”,而在“什么时候插、插坏了怎么办”。很多人卡在第一次迁移后手动psql查表,发现没数据就重跑Seed——结果重复ID、冲突主键、权限错乱。留个心眼:每次Seed前先SELECT COUNT(*) FROM roles,非零就跳过,比什么都管用。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










