应使用migrate CLI初始化迁移目录并生成首对up/down文件,命名须为000001_init_schema.up.sql和.down.sql,序号自动递增不可手动修改;已有线上库需先migrate status再migrate force 1标记版本,避免重复执行。

直接用 golang-migrate/migrate CLI 或库集成,别手写 SQL 脚本丢进 main() 里跑 —— 那不是自动化,是定时炸弹。
怎么初始化迁移目录和第一个版本文件
迁移不是从「改完代码就上线」开始的,而是从明确的起始点开始。你得先决定当前数据库状态是否算作 v1,再生成首个迁移文件。
- 用
migrate create -ext sql -dir migrations -seq init_schema生成一对.up.sql和.down.sql文件,序号自动递增 - 文件名中的数字(如
000001_init_schema.up.sql)就是执行顺序,不能手动改数字,否则多机部署时会错乱 -
.up.sql里只写 DDL 变更语句(CREATE TABLE、ADD COLUMN),不要塞INSERT初始化数据 —— 那属于 seed,应分离管理 - 如果已有线上库,先用
migrate status看当前版本,再用migrate force 1把历史状态标记为已执行,避免重复建表报错
Go 项目里集成 migrate 库时如何避免 panic
直接调 migrate.Up() 很容易在连接失败或脚本语法错误时 panic,而生产环境需要的是可观察、可中断的失败。
- 务必用
context.WithTimeout()包裹迁移调用,比如 30 秒超时:m.Up(context.WithTimeout(ctx, 30*time.Second), migrate.All) - 检查
err是否为migrate.ErrNoChange(无新迁移)或migrate.ErrLocked(被其他实例抢占锁),别一概log.Fatal - MySQL 默认不支持事务性 DDL,所以
ALTER TABLE失败时无法回滚整个 up 过程 —— 这时候.down.sql不会自动触发,得靠人工干预 - 别把
migrate.New的database.URL写死在代码里,从环境变量读取,例如os.Getenv("DB_URL")
MySQL 上执行 ADD COLUMN 为什么卡住又没报错
这是 MySQL 5.6+ 的典型表现:在线 DDL 在某些场景下仍会锁表,尤其当字段加在中间位置、或表行数超百万时,migrate 会一直等,直到上下文超时。
立即学习“go语言免费学习笔记(深入)”;
- 优先用
AFTER指定位置(如ADD COLUMN x INT AFTER id),避免重排物理结构 - 确认 MySQL 版本是否开启
ALGORITHM=INPLACE支持:5.6 仅部分支持,5.7+ 更完善,8.0 对ADD COLUMN基本无锁 -
migrate默认不传ALGORITHM,得在 SQL 脚本里显式写,例如:ALTER TABLE users ADD COLUMN status TINYINT DEFAULT 0, ALGORITHM=INPLACE, LOCK=NONE; - 本地开发用小表测试没问题,不代表线上能过 —— 建议在预发环境用真实数据量压测单条迁移耗时
最常被忽略的一点:迁移脚本不是一次性的部署动作,而是长期维护的「数据库契约」。每次改 .up.sql 都必须同步更新 .down.sql,哪怕只是删个字段 —— 因为 down 操作可能在灰度回滚、分支测试、甚至灾备恢复时被调用,没人会为你补写昨天的回滚逻辑。


















