必须从mysql服务端、laravel连接层、迁移建表、字段定义四层面同步设为utf8mb4,缺一不可;需修改my.cnf中character-set-server与collation-server,配置database.php的charset/collation/options,迁移中显式声明utf8mb4或调用schema::defaultstringlength(191),已有表用alter table convert升级并验证char_length。

要让Laravel存emoji、生僻中文、繁体字不变成???或直接报错SQLSTATE[HY000]: General error: 1366 Incorrect string value,必须从MySQL服务端、Laravel连接层、迁移建表、字段定义四个层面同步设为utf8mb4,漏掉任意一层都可能在某次插入时突然崩掉。
确认MySQL服务端已启用utf8mb4
这一步不做,后面所有Laravel配置都是白搭——连接一建立,MySQL就按老默认utf8给你发数据,PDO根本来不及干预。
打开MySQL配置文件:Linux是/etc/my.cnf或/etc/mysql/my.cnf,Windows是my.ini,在[mysqld]段下添加两行:
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
保存后必须重启MySQL服务,否则配置不生效。重启后进MySQL命令行执行:SHOW VARIABLES LIKE 'character_set_server';,输出值必须是utf8mb4,不是utf8。
如果看到character_set_server还是utf8,说明没重启或配置写错位置(必须在[mysqld]下,不能在[client]或[mysql]下)。
改Laravel连接层字符集(核心步骤)
只改.env里的DB_CHARSET=utf8mb4完全无效——Laravel官方代码里根本没读这个变量,属于长期流传的误导配置。
真正起作用的是config/database.php中mysql连接块的三项:
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'options' => [
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"
]
注意:PDO::MYSQL_ATTR_INIT_COMMAND这一行不能省,Laravel 8+在某些PDO版本下会跳过charset声明,靠它强制握手时执行SET NAMES才真正生效。
改完立刻运行php artisan config:clear,否则缓存里的旧配置还在用。
新建迁移文件时强制用utf8mb4建表
Laravel 9.x之前Schema::create()默认用utf8建表,哪怕你上面全配对了,新表还是utf8编码——这是个埋了多年的坑。
方法一:在迁移文件末尾追加原生语句
DB::statement("ALTER TABLE posts CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
方法二:全局预防,在AppServiceProvider的boot()里加一行
Schema::defaultStringLength(191); //避免索引超长报错,同时触发Laravel内部对utf8mb4的兼容路径
方法三:字段级显式声明(推荐用于关键字段)
$table->string('title')->charset('utf8mb4')->collation('utf8mb4_unicode_ci');
注意:charset()方法仅在Laravel 9.20+及之后版本可用,低版本只认collation(),而collation隐含字符集。
已有表升级字符集(线上环境慎用)
第一步:先备份整库,这条命令会锁表,大表执行期间所有写操作阻塞。
第二步:用php artisan tinker执行DB::statement("ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
第三步:逐个检查字段是否生效——有些字段类型(如ENUM、SET)需单独ALTER COLUMN,CONVERT不穿透。
第四步:验证SELECT LENGTH(name), CHAR_LENGTH(name) FROM users LIMIT 1; 若返回值不同(比如LENGTH=6但CHAR_LENGTH=2),说明仍是utf8编码,没真正升级成功。
【别用php artisan migrate:refresh重刷表】 这会删库重建,线上绝对禁止。











