根本原因是Laravel 9.x前Schema::create()忽略配置,必须显式强制:建表后用DB::statement("ALTER TABLE posts CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"),字段用collation()或addColumn()指定,并在AppServiceProvider中设Schema::defaultStringLength(191)。

迁移生成的表仍是 utf8 编码,中文和 emoji 存进去变 ??? 或直接报错 SQLSTATE[HY000]: General error: 1366 Incorrect string value,根本原因不是配置没改,而是 Laravel 迁移层默认不继承连接层的 charset 设置——必须显式穿透到表和字段级。
迁移文件里怎么强制整表用 utf8mb4
Laravel 9.x 之前版本的 Schema::create() 会忽略 config/database.php 里的 charset 配置,建表语句默认走 MySQL 的 utf8(即 utf8mb3),哪怕你连得再对也没用。
- 在迁移的
up()方法中,Schema::create()执行完后,立刻追加一行:DB::statement("ALTER TABLE `posts` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); - 不要拆到另一个迁移里执行——事务隔离可能导致锁表或顺序错乱
- 如果表有外键,先
SET FOREIGN_KEY_CHECKS = 0;再执行转换,完事再设回1
为单个字段指定 utf8mb4 collation 的写法
不是所有字段类型都支持 collation(),调用失败也不会报错,容易误以为生效了。
- 仅
string()、text()、tinyText()、mediumText()等文本类字段能用:$table->string('title')->collation('utf8mb4_unicode_ci'); -
integer()、json()、boolean()等非文本类型调用collation()会静默忽略 - 要绕过限制,可用
addColumn()+ 原生定义:$table->addColumn('VARCHAR', 'bio', ['length' => 500, 'collation' => 'utf8mb4_bin']);
为什么 migrate 报 “Specified key was too long”
这是 utf8mb4 引发的连锁反应:InnoDB 默认索引长度上限是 767 字节,而 utf8mb4 下每个字符最多占 4 字节,255 × 4 = 1020 > 767,所以默认字符串长度会爆。
- 必须在
app/Providers/AppServiceProvider.php的boot()方法里加:Schema::defaultStringLength(191); - 191 × 4 = 764 字节,刚好卡在安全线内;不设会导致
unique索引、主键或联合索引创建失败 - 这个设置只影响后续新建的迁移,已有迁移需手动改字段长度或删重建
已存在表的字段升级要避开二次损坏
直接跑 ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4 很危险——如果原数据是以 latin1 或 gbk 错误编码存的,这条命令会把它当 utf8mb4 重新解释,结果更乱。
- 先查真实字节:
SELECT HEX(title) FROM posts LIMIT 1;,返回E4B8ADE69687(“中文”的 UTF-8 十六进制)说明数据完好,只是声明错了 → 改列定义:ALTER TABLE posts MODIFY title VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - 如果返回
C4E3BAC3这类短字节,大概率是 GBK 编码 → 不能硬转,得按原始编码反向恢复 - 大表慎用
MODIFY,它会锁表重建;线上环境建议用 pt-online-schema-change 或分批更新
最常被跳过的其实是 AppServiceProvider 里的 Schema::defaultStringLength(191),它不报错、不提示,但一建索引就崩,而且错误信息完全不提字符集——这个点藏得深,修起来却要重跑全部迁移。











