执行 migrate/down 未回滚的主因是未指定步数且无可用回滚项;需检查 yii_migration 表、显式传参、在 down() 中加存在性判断,并优先采用补偿 migration 而非依赖回滚。

执行 migrate/down 为什么没回滚任何东西?
最常见的情况是:你没指定步数,而 Yii2 默认只回滚最后 1 次 migration(即 down() 方法),但前提是当前应用的 migration 版本记录里确实有可回滚项。
关键点在于:migrate/down 不是“撤销上一条命令”,而是“按版本记录倒退 N 步”。如果数据库里 yii_migration 表为空,或当前已退到最初版本,它就什么也不做,也不会报错。
- 检查迁移历史:
php yii migrate/history,确认有没有已应用但未回滚的 migration - 必须显式传参才能回滚多步,比如
php yii migrate/down 3回退最近 3 次 - 若提示
No migrations to be reverted,说明yii_migration表中记录的版本已是起点,或 migration 文件已被删但记录还在(此时需手动清理表)
migrate/down 执行失败:Exception: The table does not exist
这是典型的 down() 方法写得不健壮导致的——它试图删一个根本不存在的表、字段或索引。
Yii2 的 migration 不会自动判断对象是否存在再操作,所有 DDL 都是“直译执行”。你在 up() 里建了表,down() 里直接 $this->dropTable('post'),但如果该表因异常、手动删库或环境差异并不存在,就会崩。
- 安全写法是在
down()中加存在性判断:if ($this->db->getTableSchema('post', true) !== null) { $this->dropTable('post'); } - 对索引/外键也一样,用
$this->db->getSchema()->getTableSchema('post')->getIndexes()或原生 SQL 查是否存在再删 - 别依赖
up()一定成功——down()要能独立应对“目标对象可能不存在”的场景
回滚后新 migration 再执行,提示 Duplicate column name
这说明 down() 没彻底清理干净。常见于添加字段、索引、约束等轻量变更:你以为删了字段,其实 MySQL 的 ALTER TABLE ... DROP COLUMN 成功了,但某些版本的 Yii2 Schema 缓存或 DB 层没及时刷新,或者你漏写了某一步。
更隐蔽的问题是:migration 中混用了 createTable() 和原生 SQL,而 down() 只调用了 dropTable(),却忘了删关联的触发器、存储过程或全文索引(这些不会被 Yii 自动识别和清理)。
- 执行前先看
up()做了什么:建表?加字段?建索引?加外键?改注释?每一步都要在down()中逆向对应 - 字段级操作建议统一用
addColumn()/dropColumn(),避免手写 SQL 导致down()遗漏 - 本地调试时,可在
down()开头加var_dump($this->db->getTableSchema('xxx'));确认结构是否真被清掉
生产环境不敢随便 migrate/down?那换种思路
回滚不是唯一解。尤其在线上,直接删表或改结构风险高,migrate/down 往往只是开发阶段的便利手段。
真实项目里更稳妥的做法是:用“补偿 migration”代替回滚。比如你发现上次加的字段名错了,与其 down 再 up,不如写个新 migration,renameColumn() 或 alterColumn() 修复,然后把旧 migration 标记为“已弃用”(不删文件,也不再执行)。
- 所有线上变更必须可幂等、可验证、可监控;
down()很难满足这三点 - Yii2 不支持 migration “重放”或“跳过”,所以一旦某次 migration 在部分环境执行失败,后续都得人工对齐状态
- 真正复杂的数据迁移(如字段拆分、类型转换、历史数据清洗),
down()几乎无法可靠还原——这时候就得靠备份 + 脚本 + 业务灰度
回滚命令本身很简单,难的是让每一步 down() 都经得起删库重跑的检验。多数人卡住,不是不会敲命令,而是没想清楚“这个 down,到底要 undo 掉什么”。










