结论是Go语言学习和数据库迁移必须分开对待,因database/sql仅提供SQL执行接口、GORM.AutoMigrate仅为结构快照工具且无版本记录与回滚能力,真正生产级迁移必须依赖migrate CLI——它通过时间戳命名的成对.up/.down文件、内置锁机制、schema_migrations版本表及跨数据库兼容性保障安全可逆变更。

Go 语言学习路径里根本没“数据库迁移”这一课
初学者常误以为“学会了 Go 就能写迁移”,但 database/sql 只提供执行接口,GORM.AutoMigrate 是结构快照工具,不是迁移方案。真要上线,你得懂的是:migrate CLI 怎么用、.up.sql 里哪些 SQL 会锁表、PostgreSQL 的 BEGIN; ... COMMIT; 和 MySQL 的 ALGORITHM=INPLACE 有什么区别——这些不在语法书里,而在生产事故记录里。
为什么 migrate CLI 是唯一靠谱的选择
-
GORM.AutoMigrate不记录版本、不支持回滚、删字段无效、并发启动会报relation already exists - 手写
db.Exec循环跑 SQL 文件,等于自己造轮子绕过锁机制和版本校验 -
migrateCLI 自动建schema_migrations表、用SELECT FOR UPDATE加锁、按时间戳排序执行,且支持up/down命令对 - 它不关心你是用 Go 还是 Bash 调用,只认
.up.sql文件内容是否合法、是否成对存在
迁移文件命名和内容怎么写才不出错
命名不是风格问题,是执行顺序的硬约束:
- 必须用 14 位时间戳开头:
20240520103000_add_users_table.up.sql,不能是001_create_table.up.sql -
.up.sql和.down.sql必须成对出现,缺一个,migrate up就报错退出 -
.up.sql里别写IF NOT EXISTS这类条件判断——migrate 不解析逻辑,只原样执行;幂等性靠 SQL 本身保证(比如用CREATE TABLE IF NOT EXISTS) - 涉及字段重命名或类型变更,别用单条
ALTER TABLE RENAME COLUMN;改用三步法:CREATE TABLE new_xxx→INSERT INTO new_xxx SELECT ... FROM old_xxx→DROP TABLE old_xxx - SQLite 必须用绝对路径指定 DB 文件,否则不同工作目录下
migrate找不到.db
生产环境跑迁移前必须手动验证的三件事
自动化不能替代人脑判断:
- 在同版本数据库上手动执行:
psql -f 20240520103000_add_users_table.up.sql,观察是否卡住、是否触发ONLY_FULL_GROUP_BY报错 - 检查事务支持:PostgreSQL 允许 DDL 在事务中执行,MySQL 8.0+ 对部分
ALTER支持ALGORITHM=INPLACE,SQLite 的ALTER TABLE无法回滚——这意味着你的.down.sql可能根本撤不干净 - 确认权限:PostgreSQL 用户必须有
CREATE TABLE权限,否则migrate up会静默失败,日志里只有一行no change
.up.sql 都得想清楚:它改了什么结构、影响哪些查询、.down.sql 能不能真正清理干净、有没有其他服务正在读写这张表——这些没法靠工具自动推导,只能人来兜底。



















