Yii2 migrate 是版本化、可回滚的数据库结构与初始数据变更机制,非SQL客户端;迁移文件是代码快照,须幂等、禁业务逻辑、禁生产环境down,时间戳命名不可手动修改。

Yii2 的 migrate 不是用来“管理数据表结构”的工具,而是用来**版本化、可回滚地变更数据库结构与初始数据**的机制。直接用它做日常 DDL 调试或手动改表,大概率踩坑。
为什么 yii migrate 不能当 SQL 客户端用
很多人第一次跑 yii migrate/create create_user_table 后,随手在生成的 up() 里写 $this->execute('ALTER TABLE user ADD COLUMN...'),以为这就是“改表”。但问题来了:迁移文件本质是代码快照,不是 SQL 日志;一旦执行过 up(),再改文件内容不会自动重放——除非你手动删记录、退迁移、再跑,而 Yii2 默认不让你退已提交的迁移(除非加 --allow-unsafe)。
常见错误现象:Exception 'yii\base\InvalidConfigException' with message 'The migration history is corrupted.',基本就是手动改了已执行过的迁移文件,又没清 migration 表。
- 迁移文件必须幂等:
up()和down()要能反复执行不崩(比如建表前先if not exists,删字段前先判断字段是否存在) - 不要在迁移里写业务逻辑(如计算用户积分后插入),那该放进 seed 或命令行脚本
- 生产环境禁止用
yii migrate/up 100这种“跑全部”操作——必须明确指定版本,避免跳过中间依赖
yii migrate/create 生成的类名和文件名怎么对应
运行 yii migrate/create add_status_to_post,生成的文件是 m123456_789012_add_status_to_post.php,类名是 m123456_789012_add_status_to_post。注意:前缀 m + 时间戳(年月日时分秒微秒)是硬编码规则,不能手改时间部分,否则 Yii2 找不到执行顺序。
使用场景:多人协作时,如果两个人同时 create,时间戳可能只差毫秒,但 Yii2 按字典序排序执行——所以别靠“谁先提交谁先执行”,要用 depends 显式声明依赖。
- 想让迁移 A 在 B 之后执行?在 A 的
depends数组里写['m123456_789012_add_status_to_post'] - 时间戳重复?不会真撞,但万一有,Yii2 会报
Migration already exists,删掉重试就行 - 文件名里不能有大写字母或空格,否则 Windows 下可能加载失败(类名大小写敏感,文件系统不敏感,容易错位)
什么时候该用 yii migrate/down,什么时候不该
down() 只适合“还没上线、还没产生真实业务数据”的阶段。一旦表里有数据,dropColumn 或 dropTable 就是高危操作——Yii2 不校验字段是否被其他迁移或代码引用,也不管外键约束是否还存在。
性能影响:MySQL 下 ALTER TABLE DROP COLUMN 在大表上会锁表并重建,而 down() 里写这个,线上执行等于停服。
- 开发环境可以
down,但建议先dump当前数据,再down,再up验证 - 测试环境慎用
down:CI 流水线里如果用yii migrate/down 1回退最新一个,而那个迁移其实包含多个表变更,down()可能只清理了部分,导致状态不一致 - 生产环境原则上禁用
down;结构变更必须用“新增字段+迁移数据+废弃旧字段”的三步走,而不是删字段
迁移里怎么安全地改字段类型或加索引
直接写 $this->alterColumn('user', 'name', $this->string(128)) 看似简单,但在 MySQL 5.7+ 严格模式下,如果原字段有默认值或非空约束,可能报错 Invalid default value for 'xxx';PostgreSQL 则对类型转换更保守,text → varchar(255) 需显式 USING 转换。
兼容性影响:SQLite 对 alterColumn 支持极弱,基本只能靠 createTable + insert + dropTable 模拟,而 Yii2 的 alterColumn 底层不做这种兜底。
- 改类型优先用
$this->addColumn()新增字段 → 写迁移脚本搬数据 →$this->dropColumn()删旧字段 - 加索引务必命名:用
$this->createIndex('idx-user-status', 'user', 'status'),别依赖自动生成名,否则不同环境索引名不一致,down()时删不掉 - 涉及时间字段,统一用
$this->dateTime()而不是$this->string(),避免 MySQL 5.6 vs 8.0 的datetime默认值行为差异
真正难的从来不是写迁移命令,而是判断哪一行 SQL 该进迁移、哪一行该进 seed、哪一行根本不该动结构——尤其当 DBA 说“这个字段下周就要下线”,而你发现它正被三个微服务当主键用着。


















