migrate cli是go生态唯一稳妥的数据库迁移方案,必须使用而非可选;绕开cli自行拼sql或调go sdk会导致schema_migrations表与实际结构脱节,因并发执行、事务限制及url解析错误等引发线上故障。

migrate CLI 是唯一能稳住生产环境数据库版本的方案,不是“可选”,而是必须用它——绕开 CLI 自己拼 SQL 或调 Go SDK,90% 会导致 schema_migrations 表与实际结构脱节。
为什么不能在 Go 代码里直接调 migrate.Up()
不是语法错,是执行模型不匹配:PostgreSQL 的 CREATE TABLE、MySQL 的 ADD COLUMN 等 DDL 在事务块里可能被拒绝(尤其嵌套函数或连接池并发时),而 migrate.Up() 默认为每个文件启一个事务。
更危险的是,多个服务实例同时启动、同时调 migrate.Up(),会竞争写 schema_migrations 表,导致版本记录错乱甚至死锁。
Go 应用该只负责连库、暴露健康检查,迁移动作必须交给独立 CLI 进程执行。
- 真要嵌入代码(如 FaaS 场景),先调
m.Version()判断是否已是最新版,避免重复扫描 - 传入的
*sql.DB必须已调过db.SetMaxOpenConns(1),禁用并发迁移 - 别用
m.Up(-1),它会无限执行;明确传目标版本号,比如m.Up(2)
migrate create 生成文件时必须用 -seq 或 -ts,不能手写命名
静默跳过是最常见故障:文件名漏 .up.sql 后缀、大小写错成 .UP.SQL、序号带空格或非数字前导零(如 01_init.up.sql),migrate 都当没看见,也不报错。
混用两种命名法(比如目录里既有 000001_init.up.sql,又有 20240501_add_index.up.sql)会让排序逻辑崩坏,执行顺序不可控。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 用
migrate create -ext sql -dir ./migrations -seq init,自动生成000001_init.up.sql和.down.sql - 序号必须是 64 位无符号整数,不能跳、不能重复,也不能用时间戳和序号混放同一目录
-
.down.sql不可省略——哪怕内容就一行DROP TABLE IF EXISTS users;,否则migrate down直接失败
数据库 URL 里密码含 @ 或 / 必须转义,否则解析失败
错误示例:postgres://user:p@ss/word@localhost/db,解析器把 p@ss/word 当成 host,截断后连不到库;mysql://root:pass@123@localhost:3306/test 同理。
这不是网络不通,是 URL 解析阶段就失败,日志里往往只显示 “unknown driver” 或 “invalid URL scheme”。
- 用 Go 的
url.QueryEscape("p@ss/word")得到p%40ss%2Fword,再拼进 URL - PostgreSQL 必须带
?sslmode=disable(或按需配require),MySQL 必须加?parseTime=true - 驱动名大小写敏感:
postgres可以,Postgres或PG会报unknown driver "Postgres"
上线前必须跑 migrate validate 和 migrate version
这两个命令不改数据,但能提前暴露 90% 的部署事故:validate 检查所有 .up.sql 和 .down.sql 是否语法合法、是否成对、编号是否冲突;version 确认当前 DB 版本号,避免误 up 或漏 up。
-
migrate validate -path ./migrations—— 不依赖数据库,纯文件校验 -
migrate -database "your-db-url" -path ./migrations version—— 查当前已应用到哪一版 - CI 流程里漏掉
validate,曾有团队上线时发现某.down.sql少了个分号,导致down 1卡死,只能人工进 DB 修
真正的难点不在怎么写 SQL,而在 down 脚本能否真正逆向——删字段前得先删依赖它的索引和外键,改类型前得确保旧数据能兼容新类型,这些没法靠工具自动推导,得人盯。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










