Doctrine迁移SQL错误的直接原因是未按目标数据库方言适配语句,如MySQL 5.7不支持JSON却生成JSON字段;元数据脱节导致“表已存在”误报;strict mode下NOT NULL变更失败;PostgreSQL enum需手动管理type。

Migration执行时抛出SQL语法错误
直接原因是Doctrine在生成SQL时,没按目标数据库的实际方言(dialect)适配语句。比如你在MySQL里用json类型写迁移,但数据库版本是5.7(不原生支持JSON),Doctrine仍会生成JSON字段定义,导致SQLSTATE[42000]报错。
实操建议:
- 先确认数据库实际版本:
SELECT VERSION(),再查对应Doctrine DBAL支持矩阵(如MySQL 5.7不支持json,得改用longtext+ 应用层校验) - 别依赖
php bin/console doctrine:migrations:diff自动生成的类型——它只看PHP实体注解,不验证DB能力 - 手动编辑迁移文件里的
$this->addSql(),把高阶类型降级:比如把json换成longtext,uuid换成string(36) - 如果用了
@ORM\Column(type="json"),记得同步改platformOptions或加options={"json": false}关掉自动映射
“Table already exists”但表实际不存在
这是Doctrine迁移元数据和数据库状态脱节的典型表现,不是SQL写错了,而是doctrine_migrations表里记了某次迁移已执行,但实际建表中途失败或被手动删过表。
实操建议:
- 别急着删
doctrine_migrations表——先查它:SELECT * FROM doctrine_migrations ORDER BY version DESC LIMIT 5,看最后几条记录的version和executed_at - 对比
migrations/Version*.php文件名,找出现“断层”的版本号(比如有Version20230101000000记录,但本地没这个文件) - 用
php bin/console doctrine:migrations:sync-metadata-storage修复元存储结构(仅当doctrine_migrations表本身损坏时) - 真正要跳过某次失败迁移,用
php bin/console doctrine:migrations:mark-as-migrated <code>VersionXXX,而不是rollup或execute
MySQL strict mode导致NOT NULL字段插入NULL
Doctrine默认生成的ALTER TABLE语句,在strict mode下会拒绝给已有行补默认值,哪怕你写了nullable=false, options={"default": "''"},也会卡在SQLSTATE[HY000]: General error: 1138 Invalid use of NULL value。
实操建议:
- 迁移前临时关strict mode(开发环境):
SET SESSION sql_mode = '',但上线禁用——这属于权宜之计 - 正解是分两步:先
ADD COLUMN为nullable=true,再UPDATE填默认值,最后CHANGE COLUMN设NOT NULL - 用
php bin/console doctrine:migrations:generate手写迁移时,避免在addSql()里拼接多条语句——每条addSql()是独立事务,出错不回滚上一条 - 检查
doctrine.yaml里server_version是否准确,它影响DBAL生成SQL的严格程度(比如设成'8.0'却连着5.7,会误用窗口函数语法)
PostgreSQL中enum类型迁移失败
Doctrine对PG enum的支持很脆弱:它不会自动创建enum type,也不会检测现有type是否变更值列表。一旦实体里改了@Enum的values,迁移就报Unknown database type enum requested或type "xxx_enum" does not exist。
实操建议:
- 放弃用
@Column(type="string")配合PHP枚举——PG原生enum必须手动管理type生命周期 - 在迁移里显式处理:先
CREATE TYPE IF NOT EXISTS xxx_enum AS ENUM ('a', 'b'),再ALTER TABLE ... ALTER COLUMN status TYPE xxx_enum USING status::xxx_enum - 别在
diff后直接跑迁移——diff根本不会生成enum type语句,它只会尝试改字段类型,然后崩 - 线上加新值?不能直接
ALTER TYPE ... ADD VALUE(PG 12+才支持),老版本得建新type、迁数据、删旧type,动作必须进迁移文件,不能靠控制台命令
最麻烦的从来不是写错SQL,而是Doctrine把“数据库schema”和“PHP模型”当成一回事——它不区分逻辑类型和物理类型,也不管你DB有没有权限执行CREATE TYPE。每次迁移前,先看doctrine:migrations:status,再看SELECT * FROM pg_type(PG)或SHOW COLUMNS(MySQL),比重跑10次diff更省时间。










