根本原因是mysql连接层字符集仍为3字节utf8,未形成php、mysql服务端、laravel配置、表结构四层统一的utf8mb4闭环;需同步配置php.ini default_charset、my.cnf character-set-server、config/database.php charset/options及迁移文件字符集声明。

中文、emoji和生僻字在Laravel中存入数据库后变成???或直接报错SQLSTATE[HY000]: General error: 1366 Incorrect string value,根本原因不是PHP或Laravel写错了,而是MySQL连接层字符集仍沿用旧的utf8(即最多3字节的伪UTF-8),而MySQL服务端、PHP运行时、Laravel配置、表结构这四层未形成统一的utf8mb4闭环,其中任何一层脱节都会导致乱码。
确认并修正php.ini时区与字符集基础环境
先确保PHP底层不拖后腿——date_default_timezone_set()只影响框架内时间函数,但mbstring、iconv等扩展的默认编码行为直接受php.ini中default_charset控制。若此项为空或设为iso-8859-1,Laravel的Str::limit()、mb_strlen()等操作可能误判中文长度,间接影响JSON序列化或URL生成。
打开php.ini文件,搜索default_charset,将其值设为"utf-8"(注意是双引号包裹的utf-8,不是utf8mb4);同时确认mbstring.internal_encoding也设为UTF-8。
【必须重启PHP-FPM或Apache/Nginx服务,否则phpinfo()里看到的仍是旧值】
执行php -i | grep -E "(default_charset|mbstring.internal_encoding)"验证输出是否匹配修改后的值。
MySQL服务端强制启用utf8mb4支持
哪怕Laravel连接层设对了,如果MySQL没配好,照样乱码。重点检查三处:
Linux系统编辑/etc/mysql/my.cnf或/etc/my.cnf,Windows系统编辑C:\ProgramData\MySQL\MySQL Server X.X\my.ini,在[mysqld]段落下方追加:
character-set-server = utf8mb4collation-server = utf8mb4_unicode_ciskip-character-set-client-handshake
最后一行skip-character-set-client-handshake强制客户端服从服务端设定,防止PDO连接时被SET NAMES utf8覆盖。
重启MySQL服务:sudo systemctl restart mysql(Linux)或通过Windows服务管理器重启。
登录MySQL命令行,执行SHOW VARIABLES LIKE 'character_set%';,确认character_set_server和character_set_database两项值均为utf8mb4。
Laravel配置层显式声明utf8mb4连接参数
方法一:修改config/database.php中的mysql配置块
找到'mysql' => [...]数组,在其中添加或替换以下三项:
'charset' => 'utf8mb4','collation' => 'utf8mb4_unicode_ci','options' => [PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"],
方法二:在.env中设置(仅限Laravel 9.2+且config/database.php已适配env读取)
确保config/database.php中mysql块包含'charset' => env('DB_CHARSET', 'utf8mb4')和'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci');然后在.env中写入:
DB_CHARSET=utf8mb4DB_COLLATION=utf8mb4_unicode_ci
【注意:DB_CHARSET这个变量在Laravel 8之前版本的官方配置里根本没读,纯属误导,务必确认你用的是已支持该env变量的Laravel版本】
迁移文件与已有表结构同步升级
第一步:运行php artisan migrate:fresh前,先执行全局长度限制修复
在app/Providers/AppServiceProvider.php的boot()方法中加入:
Schema::defaultStringLength(191);
第二步:新建迁移时强制指定字符集
在迁移文件的Schema::create()之后,追加原生SQL语句:
DB::statement("ALTER TABLE posts CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
第三步:对已有表执行手动转换
逐个执行:ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;,不能只改表默认值——旧字段仍保持utf8编码,数据不会自动重编码。
第四步:检查索引长度超限问题
utf8mb4比utf8多1字节,原来255字符的VARCHAR字段建索引会超767字节上限。若遇到Specified key was too long错误,需缩短字段长度或改用innodb_large_prefix=ON(MySQL 5.7+默认开启)。
Redis与周边组件字符集联动
Redis本身不处理字符集,但PHP序列化和Laravel缓存键名若含中文,serialize()或json_encode()行为受mbstring.func_overload影响。若此ini项开启(值为2或4),strlen()会被替换成mb_strlen(),而Redis客户端底层用strlen()计算协议长度,可能导致缓存写入截断。
检查php.ini中mbstring.func_overload是否为0,不是则改为0并重启PHP服务。
对于使用cache:redis驱动的项目,在config/cache.php的stores.redis.serialize选项中,推荐设为'php_serialize'而非'json',避免中文JSON编码失败。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











