change()失败主因是数据库底层执行alter column时发现现有数据不兼容新约束,如长度超限、存在null或重复值;必须先用sql校验数据兼容性,并在迁移中完整复现原字段所有属性(如unique、nullable)才能安全执行。

直接改字段类型会丢数据或报错,必须先确认旧数据是否兼容新类型,再用 change() 安全执行。
为什么 change() 会失败?
不是语法写错,而是数据库引擎在底层执行 ALTER COLUMN 时发现现有数据不满足新约束。比如把 string(255) 改成 string(50),但已有记录邮箱长度是 120,MySQL 就会拒绝并抛出 SQLSTATE[HY000]: General error。
- 字段缩小长度(
string(255) → string(100)):必须确保所有值 ≤ 100 字符 - 加
->unique()或->notNullable():表里不能有重复值或 NULL - 改类型(
string → text):多数情况安全,但text → string可能截断 - SQLite 下几乎不支持任何
change(),会直接报Unsupported alter operation
如何验证旧数据兼容性?
别靠猜。在运行迁移前,手动查一遍:
SELECT MAX(CHAR_LENGTH(email)) FROM users; SELECT COUNT(*) FROM users WHERE email IS NULL; SELECT COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1 LIMIT 1;
这些 SQL 能快速暴露长度超限、NULL 值、重复值三类典型问题。如果结果不为 0,就得先清理数据——比如用 DB::statement() 批量截断,或加临时 migration 先处理脏数据。
change() 的写法必须完整还原字段定义
Laravel 不会“合并”新旧定义,它拿你写的整行定义去比对原结构。漏掉一个属性,就可能误删索引或默认值。
- 错的写法:
$table->string('email', 191)->change();(原字段带->unique(),这行没写,迁移后唯一索引就没了) - 对的写法:
$table->string('email', 191)->unique()->nullable()->change();(显式带上所有需保留的修饰) - 回滚方法
down()同理:必须和原字段定义完全一致,否则migrate:rollback会失败
生产环境绕不开的两个细节
一是 MySQL 的 strict mode 必须开启(config/database.php 中 'strict' => true),否则超长字符串会被静默截断,表面成功实则丢数据;二是大表执行 change() 会锁表,建议在低峰期操作,或提前在测试库用真实数据量压测耗时。真正容易被忽略的,是 doctrine/dbal 的版本——Laravel 10.4+ 推荐用 ^3.7,旧版在 MariaDB 11.4+ 上可能解析错列类型。











