必须将mysql连接层字符集显式设为utf8mb4,包括laravel配置中的charset、collation及pdo初始化命令,并同步配置mysql服务端、表结构、迁移文件、php环境及redis等周边组件,否则emoji和生僻字会乱码或报错。

刚跑通 Laravel 迁移就发现中文存进去变成 ???,emoji 直接报 SQLSTATE[HY000]: General error: 1366 Incorrect string value——这不是代码写错了,是 MySQL 连接层字符集没设对,Laravel 默认用的 utf8(实为 MySQL 的 3 字节伪 UTF-8),根本不支持生僻字和 emoji。
确认 MySQL 服务端已启用 utf8mb4
哪怕 Laravel 配置全对,MySQL 本身不支持 utf8mb4,照样乱码。必须改 my.cnf(Linux)或 my.ini(Windows)里 [mysqld] 段:
加两行:character-set-server = utf8mb4 和 collation-server = utf8mb4_unicode_ci。
重启 MySQL 服务后,进命令行执行:SHOW VARIABLES LIKE 'character_set%';,重点核对 character_set_server 和 character_set_database 两项值是否都为 utf8mb4。
如果其中一项仍是 utf8,说明配置没生效或没重启,迁移前必须解决。
修改 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 版本下仍会走默认 utf8 连接,存 emoji 时直接报错。
升级已有表和字段的字符集
服务端和 Laravel 连接层设对了,旧表还是 utf8,数据照样乱码。
方法一:手动执行 SQL 升级单张表
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
注意:只改表默认字符集不够,旧字段仍保持 utf8,必须用 CONVERT TO 全量转换。
方法二:批量升级所有表(适合多表项目)
先查出所有非 utf8mb4 的表:SELECT table_name FROM information_schema.tables WHERE table_schema = 'your_db_name' AND table_collation NOT LIKE 'utf8mb4%';
再对每张表执行上面的 CONVERT TO 语句。
强制新迁移表使用 utf8mb4
Laravel 9.x 之前,Schema::create() 默认用 utf8 建表,即使你改了 config/database.php 也不生效——这是长期被忽略的隐性行为。
第一步:在 AppServiceProvider::boot() 中加全局设置:Schema::defaultStringLength(191);(避免 utf8mb4 下索引长度超限)
第二步:在每个迁移文件的 Schema::create() 后追加显式语句:
DB::statement("ALTER TABLE posts CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
第三步:对字段级字符集做硬编码声明,例如:$table->string('title')->charset('utf8mb4');
这三步缺一不可,否则新建的表和字段仍会回落到 utf8。
检查周边组件是否同步适配
Redis、PHP 环境、甚至日志组件若未统一 utf8mb4,也可能导致中文截断或乱码。
PHP 端检查 mb_internal_encoding() 是否返回 UTF-8;如果不是,加 mb_internal_encoding('UTF-8'); 到 bootstrap/app.php 开头。
Redis 不涉及字符集,但如果你用 Redis 存序列化后的中文数组,需确认 serialize() 和 unserialize() 过程中没被转义——测试方法:存一个含中文的数组,取出来看是否完整。
最后运行 php artisan migrate:fresh,插入一条含 emoji 的记录(如 "✅ 测试成功"),再查出来验证显示是否正常。











