golang-migrate CLI驱动SQL迁移最稳,GORM AutoMigrate仅适合开发验证;因其不记录版本、不支持回滚、无法控制顺序,字段变更或删列时行为不可控,且静默跳过约束冲突,导致线上schema与代码脱节。

用 golang-migrate 做 CLI 驱动的 SQL 迁移是最稳的选择,GORM 的 AutoMigrate 只适合开发初期快速验证,不能替代版本化迁移。
为什么不能依赖 GORM AutoMigrate 做生产迭代
AutoMigrate 本质是“结构对齐”,不是“版本演进”:它不记录历史、不支持回滚、无法控制执行顺序,且在字段类型变更(如 VARCHAR(100) → VARCHAR(255))或删除列时行为不可控。更关键的是,它会静默跳过约束冲突(比如已存在同名索引),导致线上环境表状态与代码模型脱节。
- 执行
db.AutoMigrate(&User{})后,你无法知道这次操作实际执行了哪几条 DDL - 没有
schema_migrations表,团队无法判断测试环境是否和生产环境处于同一 schema 版本 - MySQL 中
ALTER TABLE ... MODIFY COLUMN可能锁表,而AutoMigrate不提供事务边界或超时控制
golang-migrate CLI 初始化与文件命名规范
迁移文件必须严格按序号+描述+方向命名,否则 migrate 会跳过或乱序执行。推荐用 -seq 模式生成,避免时间戳冲突:
- 运行
migrate create -ext sql -dir db/migrations -seq add_status_column - 生成两个文件:
000001_add_status_column.up.sql和000001_add_status_column.down.sql - 序号不能跳(如从
000001直接到000003),也不能重复;每个序号只能有一组.up.sql/.down.sql - SQL 文件里不要写
USE database_name—— 连接 URL 已指定库,多此一句可能在某些 MySQL 版本报错
Go 程序内调用 migrate.Up 的避坑要点
直接在 main() 里调 m.Up() 很常见,但容易卡死或报错,核心问题在事务与连接池:
立即学习“go语言免费学习笔记(深入)”;
- PostgreSQL 下 DDL 语句(如
CREATE TABLE)不能嵌套在事务块中,而默认migrate会给每个.up.sql包一层事务 → 必须禁用:migrate.WithInstance(db)+ 手动传入已配置好的*sql.DB -
db.SetMaxOpenConns(1)是硬性要求,否则并发执行迁移时会竞争schema_migrations表锁 - URL 中必须带驱动参数:
mysql://user:pass@tcp(127.0.0.1:3306)/mydb?parseTime=true&loc=Local,漏掉parseTime=true会导致时间字段解析失败 - 别用
m.Up(-1),它会无限重试失败的迁移,应明确传0(只执行未应用的)或具体步数
Down 回滚失败后如何手动修复状态
migrate down 不是原子回滚:只要某条 .down.sql 执行成功,其版本号就会写入 schema_migrations 表,哪怕后续步骤失败。此时再 up 会跳过已标记的版本,导致结构不一致。
- 先查当前状态:
migrate -path db/migrations -database "mysql://..." version - 若发现
schema_migrations记录了已失败的 down 版本,手动从该表删掉对应行(DELETE FROM schema_migrations WHERE version = '000002') - 再检查数据库实际结构,补全缺失的
DROP或ALTER操作(比如外键没删干净就去删主表,得先删外键) - 最后用
migrate force 000001强制重置到上一版,再up重试
最易被忽略的是迁移文件的执行边界——每个 .up.sql 必须是单个可独立执行的 DDL 单元,不能塞多个 CREATE TABLE 加一个 CREATE INDEX;因为一旦中间出错,schema_migrations 表只会记下“已开始”,不会记“已执行到第几行”。


















