用 migrate 命令行工具做迁移最稳,因其纯 CLI + SQL 文件驱动、版本控制友好、回滚可靠;自行拼 SQL 易导致 schema_migrations 表与实际结构脱节。

用 migrate 命令行工具做迁移最稳,别自己写 SQL 执行器
Go 生态里没有像 Django 或 Rails 那样内置迁移的框架,migrate 是事实标准。它不依赖 Go 代码,纯 CLI + SQL 文件驱动,版本控制友好、回滚可靠。自己用 database/sql 拼 SQL 执行顺序,极易漏掉锁表、事务边界或状态记录,导致 schema_migrations 表和实际结构脱节。
- 安装:直接
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.15.2/migrate.linux-amd64.tar.gz | tar xvz(Mac/Win 换对应二进制) - 迁移文件命名必须是
000001_create_users.up.sql和000001_create_users.down.sql,序号决定顺序,不能跳号也不能重复 - 数据库 URL 必须带
?sslmode=disable(PostgreSQL)或?parseTime=true(MySQL),否则连接直接失败 - 执行迁移前先跑
migrate -path ./migrations -database "postgres://..." version确认当前版本,避免误操作
migrate.Up 在 Go 代码里调用时,记得关掉自动事务
有些场景需要在服务启动时同步拉起迁移(比如 FaaS 或短生命周期任务),这时得用 Go SDK。但默认 migrate.Up 会为每个 SQL 文件启一个事务——而 PostgreSQL 的 CREATE TABLE 等 DDL 语句在事务块里可能报错:ERROR: CREATE TABLE cannot be executed from a function(尤其在函数内嵌调用时)。
- 解决方案:初始化
migrate.New时传入migrate.WithLogger和migrate.WithStdLogger,再用migrate.WithInstance包装已有的*sql.DB实例 - 关键点:调用
migrate.Up前,确保 DB 连接池已建好,且db.SetMaxOpenConns(1)—— 多连接并发执行迁移会竞争schema_migrations表锁 - 示例片段:
m, err := migrate.New("file://./migrations", "postgres://localhost/db?sslmode=disable") if err != nil { ... } m.Log = &migrate.DefaultLogger{} err = m.Up() // 不要传 -1,避免无限执行
Down 迁移失败后,schema_migrations 表状态容易卡住
migrate down 不是“撤销上一次 SQL”,而是按序号倒着执行 .down.sql。一旦某条 down 脚本出错(比如删表时发现外键约束未清除),工具会停住,但已成功执行的 down 步骤仍会写入 schema_migrations 表,导致后续 up 认为“这部分已应用”,跳过关键修复步骤。
- 检查手段:直接查
SELECT * FROM schema_migrations ORDER BY version DESC LIMIT 5,看最新几条是否和本地文件序号对得上 - 修复方式:手动
DELETE FROM schema_migrations WHERE version = XXX(仅限开发环境),再重试down;生产环境必须补全健壮的down.sql,比如删外键优先于删主表 - 预防建议:每个
.down.sql开头加DO $$ BEGIN ... EXCEPTION WHEN undefined_table THEN NULL; END $$;(PostgreSQL),忽略不存在对象的错误
SQLite 迁移路径里不能用相对路径,否则 file:// 解析失败
本地开发常用 SQLite,但 migrate 对 file:// 的路径解析非常字面——它不会把 ./migrations 自动转成绝对路径,运行时直接报 no such file or directory,哪怕文件明明存在。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法:启动命令中用
$(pwd)/migrations(Linux/macOS)或%cd%\migrations(Windows)展开为绝对路径 - Go 代码里更稳妥:用
filepath.Abs("./migrations")拼出路径,再传给migrate.New("file://" + absPath, ...) - 注意:SQLite 的数据库文件路径也必须是绝对路径,
file:test.db这种相对写法在 migrate 里不被识别
事情说清了就结束。版本迁移不是“跑完命令就完事”,关键是每次变更后立刻验证 schema_migrations 表、SQL 执行结果、以及下游服务是否能正常连表——这三个点断掉任意一个,都比语法错误更难排查。










