直接用alterColumn()改字段类型最稳妥,它底层生成ALTER TABLE ... MODIFY COLUMN或CHANGE COLUMN语句一步到位;必须确保目标类型兼容原值,且up()与down()需配对编写逆操作,避免回滚失败。

直接用 alterColumn() 改字段类型最稳妥
Yii2 的迁移机制不鼓励“先加列再删旧列”这种三步法,尤其对已有数据的表——容易出错、事务不原子、中间状态难回滚。正确做法是直接调用 $this->alterColumn(),它底层生成 ALTER TABLE ... MODIFY COLUMN 或 CHANGE COLUMN 语句,一步到位。
- 必须确保目标类型兼容原值:比如把
INT改成DECIMAL(10,2)是安全的;但若原字段含非数字字符串(如'N/A'),MySQL 会转成0,且不报错 - 字段名和表名要严格匹配大小写,MySQL 在 Linux 下区分大小写,写错会导致
SQLSTATE[42S02]: Base table or view not found - 别在
up()里只写alterColumn()就完事——down()必须写对应逆操作,比如改回integer,否则回滚失败
迁移里怎么写 DECIMAL 类型才跨库兼容
不能写 'DECIMAL(10,2)' 这种原生 SQL 字符串。Yii2 要求用抽象类型 + 附加参数,否则换 PostgreSQL 就报错。
- 正确写法:
$this->decimal(10, 2)—— 这是Schema::TYPE_DECIMAL的封装,会自动映射到 MySQL 的DECIMAL(10,2)、PostgreSQL 的NUMERIC(10,2) - 如果需要指定默认值或非空约束,得链式调用:
$this->decimal(10, 2)->notNull()->defaultValue(0) - 错误示范:
"DECIMAL(10,2) NOT NULL DEFAULT 0"—— 这在 SQLite 或 SQL Server 下直接解析失败
已有数据时,alterColumn() 会丢精度吗
不会自动丢,但取决于你定义的精度是否够用。比如原字段存 12345.6789,你改成 DECIMAL(6,2),MySQL 会四舍五入截断为 12345.68,且不警告。
- 执行前先查最大长度:
SELECT MAX(LENGTH(CAST(amount AS CHAR))), MAX(LENGTH(SUBSTRING_INDEX(amount, '.', -1))) FROM sales;看整数位和小数位最多几位 - 建议留余量:原字段小数部分最长 3 位,就设
DECIMAL(12,4),避免上线后因精度不够导致业务计算偏差 - 测试环境务必用真实数据集跑一遍迁移,光看 DDL 语句看不出隐式截断
生产环境改 decimal 字段必须绕开的坑
线上表数据量大时,ALTER TABLE ... MODIFY COLUMN 会锁表,尤其 MySQL 5.6 及更早版本是 copy-on-write 模式,可能卡住写入几小时。
- 优先用
pt-online-schema-change工具做无锁变更,再用 Yii2 迁移标记“已执行”,别让yii migrate直接触发 DDL - 迁移文件里禁用
$this->execute('SET SESSION lock_wait_timeout = 1')这类临时设置——它不生效,MySQL ALTER 不走 session timeout 控制 - 如果必须用 Yii2 原生命令,加
--interactive=0并提前在up()开头加检查:if ($this->db->getTableSchema('sales')->columns['amount']->type !== 'decimal') { ... },防止重复执行
price 字段,迁移文件里 up() 长这样:
$this->alterColumn('product', 'price', $this->decimal(12, 4)->notNull()->defaultValue(0));
注意 down() 得配对写回原类型,而且得确认原类型是什么——别假设是 integer,先查 schema 记录。线上改字段,最麻烦的从来不是语法,而是没人记得这列当初建的时候有没有 default、是不是 unsigned、有没有被触发器引用。


















