yii2迁移是带执行顺序约束、需人工维护幂等性、默认禁止生产回滚的版本化数据库同步机制;文件名时间戳不可修改,类名须严格匹配,依赖需显式声明,up/down须自行校验存在性,safeup不保障ddl安全,生产环境必须指定版本号执行。

yii migrate 不是 SQL 客户端,也不是“改表工具”,它是一套**带执行顺序约束、需人工维护幂等性、默认禁止回滚生产变更**的版本化数据库结构同步机制。直接在已提交的迁移里补字段、删列、改类型,十有八九会触发 InvalidConfigException: The migration history is corrupted。
迁移文件命名和类名必须严格匹配时间戳
运行 yii migrate/create add_status_to_post 后生成的文件是 m260419_170000_add_status_to_post.php(当前时间),类名是 m260419_170000_add_status_to_post。这个前缀不是装饰,是 Yii2 排序执行的唯一依据:
- 时间戳部分(
m260419_170000)不能手动修改,改了就找不到、不执行、或乱序 - 文件名含大写字母(如
AddStatusToPost)在 Windows 下可能加载失败——类名大小写敏感,但 NTFS 不区分大小写 - 两个人同时
create,时间戳只差毫秒,Yii2 按字典序排,不是按提交顺序;依赖关系必须用public $depends = ['m260419_165999_another_migration'];显式声明
up() 和 down() 必须自己保证幂等,框架不帮你校验
常见错误是在 up() 里写 $this->addColumn('user', 'status', $this->integer());,然后上线后发现字段已存在,再跑就报错 “Column already exists”。正确做法是加判断:
- 建表前:用
$this->db->getTableSchema('user', true) === null判断表是否存在 - 加字段前:查
$this->db->getTableSchema('user')->getColumn('status') === null - 删字段/表前:
if ($this->db->getTableSchema('user')) { $this->dropColumn(...); } -
down()里不要写return false;就完事——如果真不能回滚,至少抛出异常说明原因,别让后续迁移误以为成功
safeUp() / safeDown() 是事务包装,不是“更安全”的代名词
safeUp() 确实自动开启事务,但事务只覆盖该方法内执行的语句。它救不了这些场景:
- MySQL 大表
dropColumn仍会锁表重建,事务包不住 DDL 阻塞 - 外键没显式删就
dropTable,事务内照样报错中断,但表可能已删一半 - 多个表关联变更(如先删子表外键,再删主表),必须在一个
safeUp()里写全,拆到两个迁移里事务不跨文件 - 涉及数据迁移(如把
status字段从 int 改成 enum 并重映射值),safeUp()无法防止中间状态数据不一致——这种该用 seed 或命令行脚本
生产环境执行迁移必须指定版本号,禁用无参数 migrate
在生产环境跑 yii migrate(不带参数)等于默许跳过所有中间迁移记录,一旦漏掉某个 depends 依赖,后续迁移直接崩。正确姿势是:
- 上线前,CI 流水线固定执行
yii migrate/up 1 --interactive=0,每次只跑一个,日志可追溯 - 回滚只允许
yii migrate/down 1 --allow-unsafe,且仅限刚发布、尚无真实业务数据的窗口期 - 已上线的迁移,哪怕只是补个索引,也绝不能改
up()再重跑——应新建迁移文件,用createIndex()单独处理 - 检查是否遗漏:用
yii migrate/history对比 Git 提交记录,确认每个迁移文件都已在目标环境执行











