根本原因是 Doctrine 依赖 schema:update 的逻辑识别差异,但其与 migrations:diff 机制不完全等价;常见于未对齐数据库状态、省略字段长度、未注册自定义类型、忽略 collation 变更等。

doctrine:migrations:diff 为什么没生成预期的迁移文件
根本原因通常是 Doctrine 没法准确识别当前数据库状态和实体定义之间的差异——它依赖 doctrine:schema:update --dump-sql 的逻辑,但该逻辑不完全等价于迁移器的 diff 机制。
常见错误现象:doctrine:migrations:diff 什么都没输出、生成空迁移类、或漏掉字段变更(尤其是索引、默认值、ENUM 类型)。
- 确保已执行
doctrine:schema:update --force或至少doctrine:schema:validate通过,让数据库与实体“暂时对齐” - 检查实体中是否用了
@ORM\Column(type="string", length=255)这类显式长度声明;省略length会导致 MySQL 下被推断为text,diff 时可能跳过 - ENUM、JSON、自定义类型必须在
Doctrine\DBAL\Types\Type中正确注册,否则 diff 会忽略类型变更 - 如果用的是 MySQL 8+,注意
collation和charset变更默认不触发 diff,需手动在@ORM\Column加options={"collation"="utf8mb4_unicode_ci"}
迁移文件里怎么安全改主键或删字段
Doctrine 默认生成的 down() 方法只是反向执行 up(),但删字段、改主键这类操作不可逆,直接回滚会丢数据或报错。
使用场景:上线后发现某字段设计错误,需删除;或要把单主键换成复合主键。
立即学习“PHP免费学习笔记(深入)”;
- 永远不要信任自动生成的
down()—— 手动重写它。例如删字段前,down()应先ADD COLUMN回去,再恢复旧数据(如有备份表) - 改主键必须分两步迁移:第一步加新字段 + 建唯一约束;第二步删旧主键 + 改新字段为
PRIMARY KEY。不能挤在一个迁移里,否则 MySQL 会拒绝 - 涉及数据迁移时(如字段拆分、合并),在
up()里用原生 SQL 写$this->addSql("UPDATE ..."),别依赖 ORM;同时给 SQL 加注释说明风险,比如// 注意:此语句不可回滚,请确认已备份 - 生产环境执行前,先在测试库跑
doctrine:migrations:execute --down [version]验证down()是否真能成功
如何避免多个开发者提交冲突的迁移版本号
Doctrine 默认用时间戳(如 20240521103000)做迁移文件名,但多人并行开发时,秒级重复极常见,导致 doctrine:migrations:migrate 报错 “Migration not found” 或跳过某些文件。
- 强制统一本地时区并启用
use_transaction:在doctrine_migrations.yaml里设transactions: true,让每次 migrate 包在事务里,失败自动回滚 - 不用默认命名,改用带前缀的命令:运行
php bin/console doctrine:migrations:generate --filter="user_profile_fix",再手动重命名为20240521103000_user_profile_fix.php,靠语义而非时间排序 - CI 流程中加检查:用脚本 grep 所有未提交的迁移文件名,若发现时间戳重复(如两个
20240521103000),立刻 fail 构建 - 团队约定:每日早会后第一人执行一次
doctrine:migrations:sync-metadata-storage并提交,其他人拉取后再生成新迁移
生产环境执行迁移时,ALTER TABLE 被锁住怎么办
MySQL 在执行 ALTER TABLE(尤其加索引、改字段类型)时会锁全表,高流量下直接拖垮服务。Doctrine 不会自动避开这点,它只管按顺序跑 SQL。
性能影响:一个 500 万行的用户表加索引,可能锁表 3 分钟以上。
- 禁用 Doctrine 自动生成 DDL:在迁移文件里,把
$this->addSql("ALTER TABLE user ADD INDEX...")换成调用 pt-online-schema-change 或 gh-ost 的封装命令(需提前部署工具) - 对大表操作,拆成多阶段:先建影子表 → 同步增量 → 切换表名。Doctrine 迁移本身不支持,得用
$this->addSql()手写对应 SQL,并加超时判断 - 在迁移文件顶部加注释标明影响范围,例如
// ⚠️ 影响 user 表(约 420w 行),预计耗时 90s,建议低峰期执行,运维看到会主动规避高峰 - 永远不要在迁移里写
DROP TABLE或TRUNCATE—— 这类操作无法被 pt 工具接管,且无事务保障
最麻烦的不是语法对不对,是每个 ALTER 背后藏着的存储引擎行为、binlog 格式、复制延迟。别指望一个 migrate 命令包打天下。











