migrate down 1 没回滚是因为工具静默跳过缺失、为空、权限不足或语法错误的对应版本.down.sql文件,而非报错;需先确认当前最高已应用版本及其.down.sql存在且正确。

migrate down 1 为什么没回滚?
执行 migrate down 1 后数据库结构没变,不是工具坏了,而是它根本没执行任何 SQL —— 工具只找当前最高已应用版本(比如 1085649617)对应的 1085649617_xxx.down.sql,如果这个文件缺失、为空、权限不足,或内容语法错误,它会静默跳过,不报错也不写日志。
- 先查当前最高版本:
migrate -database "mysql://..." -path ./migrations version - 再确认该版本的
.down.sql文件存在且非空,路径必须严格匹配(不能多空格、不能大小写错) - MySQL 下特别注意:DDL 不支持事务回滚,所以
.down.sql必须用DROP INDEX IF EXISTS、ALTER TABLE ... DROP COLUMN(仅 MySQL 5.7+)这类幂等语句,不能依赖“失败就回退”
down.sql 写错会导致后续 up 失败
回滚脚本不是 up.sql 的“逆向拼写”,而是要精确抵消副作用。常见错误是删了表但漏删索引,或删字段时没处理外键约束,导致下次 migrate up 执行到同名索引/约束时报错。
-
CREATE TABLE users (...)→ 对应DROP TABLE IF EXISTS users;,不是TRUNCATE users; -
CREATE UNIQUE INDEX idx_email ON users(email);→ 必须配DROP INDEX idx_email ON users;,漏掉就会卡住后续迁移 - PostgreSQL 可以把
.down.sql包在BEGIN; ... COMMIT;里,MySQL 不行,得靠语句本身可重试
回滚中途失败后数据库变 dirty 怎么办?
只要某条 .down.sql 里的语句执行成功(比如删掉了索引),它的版本号就会被从 schema_migrations 表中移除;但如果下一条语句失败(如外键冲突),整个迁移就停住,状态变成 dirty,后续所有 migrate 命令都会拒绝执行。
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
- 先手动修复残留:比如
.down.sql里写了删列但没删干净,就得手动ALTER TABLE ... DROP COLUMN - 再用
migrate force 1085649617把版本号强行设回目标值(仅同步元数据,不执行 SQL) - 切记:force 只能用于修复状态,绝不能在生产环境乱用——必须确保数据库实际结构和目标版本一致,否则下次
up会因 DDL 冲突直接失败
GORM AutoMigrate 不能替代 migrate
别指望 db.AutoMigrate(&User{}) 能做生产环境的版本演进。它不记录历史、不支持回滚、无法控制顺序,字段类型变更(如 VARCHAR(100) → VARCHAR(255))行为不可控,还会静默跳过约束冲突(比如同名索引已存在),导致线上 schema 和代码模型脱节。
立即学习“go语言免费学习笔记(深入)”;
-
AutoMigrate是“结构对齐”,migrate是“版本演进”——前者适合本地快速验证,后者才是生产唯一可信方案 - CI 流水线里一旦用了
AutoMigrate,团队就再也无法判断测试库是否和生产库处于同一 schema 版本 - MySQL 中
ALTER TABLE MODIFY COLUMN可能锁表,而AutoMigrate不提供超时或事务边界,风险不可控
.down.sql 都得人工验证:它删得干净吗?有没有遗漏依赖?执行后数据库还能正常 up 吗?这些没法自动化,只能靠人盯。

















