mysql不支持直接修改enum字段值列表,正确做法是用change column重定义并包含所有现有值;yii2中需手写原生sql迁移、同步更新模型验证规则与常量,并清空schema缓存。

Yii2中直接修改数据库enum字段会失败
MySQL的ENUM类型不支持用ALTER TABLE ... MODIFY COLUMN直接变更枚举值列表(尤其删值或重排顺序时),执行迁移会报错Incorrect table definition; all ENUM values must be non-empty strings或Cannot change column 'status' from ENUM to ENUM。这不是Yii2的问题,而是MySQL底层限制——它要求必须重建列,且新定义不能丢失原有合法值。
正确做法:用CHANGE COLUMN + 完整枚举值列表
必须显式写出当前所有合法值(包括旧值和新增值),并用CHANGE COLUMN而非MODIFY COLUMN。Yii2迁移中需手写原生SQL,不能依赖$this->alterColumn()自动推导:
public function safeUp(Pdo $db)
{
// 假设原enum为 ENUM('draft','published'),要加'archived'
$db->createCommand("ALTER TABLE `post` CHANGE `status` `status` ENUM('draft','published','archived') NOT NULL DEFAULT 'draft'")->execute();
}
- 必须包含全部现有值,漏掉
'draft'会导致已有draft数据被转成空字符串或NULL(取决于SQL模式) -
DEFAULT值必须在新枚举列表中存在,否则报错 - 如果原字段允许NULL,语句末尾要加
NULL而非NOT NULL
PHP模型层要同步更新规则和常量
数据库改完只是第一步,Yii2模型里的验证和显示逻辑也得跟上,否则表单提交或ActiveRecord::getAttributeLabel()会出问题:
- 在
rules()里更新in验证规则:['status', 'in', 'range' => ['draft', 'published', 'archived']] - 若用常量管理枚举值(推荐),需同步增补
const STATUS_ARCHIVED = 'archived';并更新getStatusList()返回数组 - 数据库迁移成功后,立刻清空Yii2的schema缓存:
./yii cache/flush schema,否则getTableSchema()可能仍读取旧结构
更安全的替代方案:别用ENUM
长期看,把ENUM换成VARCHAR + 外键关联字典表或应用层约束更可控。Yii2迁移中只需改字段类型+加索引,无兼容风险:
$this->alterColumn('{{%post}}', 'status', $this->string(32)->notNull());
$this->createIndex('{{%idx-post-status}}', '{{%post}}', 'status');
真正麻烦的是历史数据校验和前端下拉选项同步——这比修一条SQL迁移更耗时,但后续每次增删状态都不再需要DBA介入或停机。











