必须在pdo初始化阶段执行set names指令,否则连接层仍用默认字符集解码,导致中文生僻字和emoji乱码或报错1366;仅设config/database.php的charset无效,因该配置不改变连接建立后的实际通信编码,需显式添加options数组中的pdo::mysql_attr_init_command指令。

要让Laravel真正用上utf8mb4连接字符集,必须在PDO初始化阶段就强制执行SET NAMES指令,否则即使数据库、表、迁移都设成utf8mb4,中文生僻字和emoji仍会变成???或触发SQLSTATE[HY000]: General error: 1366错误。
为什么只改config/database.php里的charset不够
因为Laravel的PDO连接器在建立连接后,会先走MySQL服务端默认字符集(通常是latin1或utf8),再读取配置中的charset字段——但这个字段只影响后续查询的字段编码声明,不改变当前连接层的实际通信编码。结果就是:插入语句用utf8mb4编码发过去,MySQL却按latin1解码,一存就乱。
这一步漏掉,所有后续配置都是纸老虎。
PDO初始化命令必须显式写进options数组
打开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是常量,不能加引号;字符串值里不能拼错utf8mb4,少个b或写成utf8都会失效。
这行代码的作用,是在每次PDO new PDO()之后、执行第一条SQL之前,立即发送一条SET NAMES指令,把本次TCP连接的客户端、连接层、结果集三重编码全部锁定为utf8mb4。
验证PDO是否真的执行了初始化命令
方法一:用tinker实时抓取底层PDO对象
运行php artisan tinker → 输入DB::connection()->getPdo() → 成功返回PDO实例后,再输入DB::connection()->getPdo()->getAttribute(PDO::ATTR_CLIENT_VERSION)
方法二:开启MySQL通用日志确认指令执行
在MySQL配置文件中临时加入general_log = 1、general_log_file = /tmp/mysql-general.log,重启MySQL,再执行一次DB::table('users')->first(),然后cat /tmp/mysql-general.log | grep "SET NAMES"
必须看到SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci出现在日志最顶部,紧挨着连接建立时间戳,才算生效。
绕过Laravel配置直连PDO测试
创建一个临时测试脚本storage/test-pdo.php:
$dsn = 'mysql:host=127.0.0.1;dbname=testdb;charset=utf8mb4';
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"
];
$pdo = new PDO($dsn, 'root', '', $opt);
var_dump($pdo->query("SELECT @@character_set_client, @@character_set_connection, @@character_set_results")->fetch());
运行php storage/test-pdo.php,输出三个字段必须全是utf8mb4,缺一不可。
如果其中任一项是utf8或latin1,说明PDO初始化命令没被识别或执行失败,检查PHP版本是否低于7.4(旧版不支持该选项)、MySQL服务端是否禁用了init_command(需在my.cnf中设置init_connect为空或移除限制)。











