最稳方式是直接用 migrate CLI 执行迁移文件,因其内置锁、版本记录和幂等控制,可避免并发导致的 relation already exists 或 duplicate column 错误;执行 migrate -database "postgres://..." -path ./migrations up 会按时间戳顺序自动运行未执行的 .up.sql 文件,且要求 migrations 目录下必须成对存在 .up.sql 和 .down.sql 文件,否则报错退出。

用 migrate CLI 导入 SQL 文件最稳
别写 Go 脚本读文件再 db.Exec,直接用 migrate CLI 执行迁移文件。它内置锁、版本记录和幂等控制,避免并发执行导致 relation already exists 或 duplicate column 错误。
-
migrate -database "postgres://..." -path ./migrations up会自动按时间戳顺序执行所有未运行的.up.sql文件 - 确保
migrations/目录下成对存在20240520103000_add_users.up.sql和.down.sql,否则 CLI 报错退出 - PostgreSQL 用户注意:数据库用户必须有
CREATE TABLE权限,否则.up.sql里建表会静默失败 - MySQL 8.0+ 支持部分 DDL 事务,但 SQLite 的
ALTER TABLE仍无法回滚——.down.sql不一定真能撤回
Go 代码里调用 migrate 必须加版本检查和超时
Serverless 或嵌入式场景下需在代码中触发迁移,但 migrate.Up() 不能裸调,否则多个实例同时启动会撞锁或重复执行。
- 先调
m.Version()获取当前版本,只对目标版本差做单步迁移:m.Migrate(1) - 用
context.WithTimeout(ctx, 30*time.Second)包裹迁移调用,防止长事务卡住应用启动 -
migrate.New()第二个参数必须传已初始化的*sql.DB实例,别传连接字符串——否则每个迁移都新建连接,破坏锁机制 - PostgreSQL 下建议
db.SetMaxOpenConns(1),保证迁移串行执行
.up.sql 和 .down.sql 写法直接影响能否安全回滚
迁移不是“写完 SQL 就完事”,每条 .up.sql 都得想清楚副作用,.down.sql 必须能完全抵消它。
- 命名必须是
YYYYMMDDHHIISS_description.up.sql,时间戳决定执行顺序;description不能含空格或特殊字符 -
.up.sql里禁止条件逻辑(如IF NOT EXISTS),SQL 就是确定性指令,靠工具保证幂等 - 重命名字段或改类型属于高危操作,优先用三步法:
CREATE TABLE new_users→INSERT INTO new_users SELECT * FROM users→DROP TABLE users,而不是单条ALTER TABLE RENAME COLUMN - 如果
.up.sql插入了初始化数据,.down.sql必须DELETE WHERE version = '20240520103000'精确清理,不能TRUNCATE
生产环境跑迁移前必须手动验证
工具不会替你判断业务影响,真正难的是人脑决策:这条变更会影响哪些查询?有没有服务正在读写这张表?down 之后数据一致性怎么保障?
立即学习“go语言免费学习笔记(深入)”;
- 用同版本数据库手动执行一遍
.up.sql和.down.sql,观察是否报错、耗时、锁表 - MySQL 生产库需提前设置
innodb_lock_wait_timeout,避免迁移卡住导致应用启动超时 - DDL 是否在事务中执行,不能依赖 migrate 默认行为;关键迁移开头写
BEGIN;,结尾写COMMIT;或ROLLBACK; -
AutoMigrate不是迁移工具,它不记录历史、无法 down、会丢数据——上线部署绝对不能用


















