最稳生产方案是用golang-migrate/migrate cli+sql文件,禁用gorm automigrate;因其不记录历史、无法回滚、字段重命名会丢数据,且不支持多环境版本追踪与审计。

直接上结论:用 golang-migrate/migrate CLI + SQL 文件是最稳的生产方案,别在 Go 代码里手写迁移逻辑,也别依赖 GORM AutoMigrate 上线。
为什么不能用 GORM AutoMigrate 做生产迁移
AutoMigrate 不是迁移工具,它只是按当前 struct “覆盖式同步”表结构。上线后多跑一次可能删字段、丢数据,且无法回滚、不记录变更历史。
- 加个
NOT NULL字段但没设默认值?直接报错中断,服务起不来 - 改字段名或拆表?
AutoMigrate会删旧列建新列,历史数据全丢 - 预发环境误执行两次?线上结构可能被悄悄破坏,审计无迹可循
- 没有 SQL 日志,出问题时连“到底执行了哪条语句”都查不到
CLI 迁移命令必须带的参数和陷阱
执行 migrate up 前,URL 和路径配置稍有偏差就会静默失败或连错库。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- MySQL 连接串必须含
?parseTime=true&loc=Local,否则时间类型解析异常 - PostgreSQL 必须加
?sslmode=disable(本地开发)或?sslmode=require(生产),否则连接拒绝 -
-path指向的是迁移文件目录(如./migrations),不是单个文件 - 执行前先跑
migrate -path ./migrations -database "mysql://..." version,确认当前版本号,避免误up或down
SQL 迁移文件命名和执行顺序怎么才算合规
序号决定执行顺序,不是时间戳也不是文件名任意排序——migrate 只认前缀数字。
- 合法命名:
000001_create_users.up.sql、000002_add_index.down.sql - 禁止跳号(如从
000001直接到000003),否则中间版本会被跳过 - 禁止重复序号,哪怕
.up.sql和.down.sql分开也不行 - 文件内不要写
-- +migrate Up这类注释,golang-migrate不识别这种语法(那是golang-migrate早期 fork 的遗留写法)
Go 代码里嵌入 migrate 要绕开的三个坑
想在服务启动时自动迁移?可以,但默认行为极易出错。
-
db.SetMaxOpenConns(1)必须设,否则并发连接会抢schema_migrations表锁,导致状态错乱 - PostgreSQL 下 DDL(如
CREATE TABLE)不能包在事务里,得关掉 migrate 自动事务:migrate.WithInstance(db, &mysql.Mysql{})而非migrate.New() -
migrate.Up()别传-1,它会无限执行所有未应用的 up 脚本;明确传0(只执行一个)或留空(执行全部)
真正麻烦的不是写 SQL,而是确保每次 up 和 down 都幂等、可逆、可审计。文件命名错一位、连接参数漏一个 &、DB 连接池没限流——都可能让迁移卡在中间状态,后续操作全失效。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










