根本原因是mysql连接层未显式设utf8mb4且redis序列化与驱动配置不匹配:须在config/database.php的mysql块中添加'charset'=>'utf8mb4'、'collation'=>'utf8mb4_unicode_ci'及'options'=>[pdo::mysql_attr_init_command=>"set names utf8mb4 collate utf8mb4_unicode_ci"],并确保redis的serialize=true与client类型(phpredis/predis)严格一致。

当 Laravel 应用在 MySQL 中存 emoji 或生僻中文时出现 ???,或 Redis 缓存取出来是 null、乱码、对象丢失属性,根本原因不是编码没改全,而是数据库连接层字符集未显式设为 utf8mb4,且 Redis 序列化方式与驱动配置不匹配——这两处错一个,缓存数据就不可逆损坏。
MySQL 连接层强制设 utf8mb4
只改表和数据库默认字符集没用,Laravel 建立 PDO 连接时仍走 MySQL 的旧 utf8(3 字节),导致写入阶段就被截断。
打开 config/database.php,在 mysql 配置块里添加三项:
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'options' => [PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"]
【必须加 options 这一行】 否则 Laravel 8+ 在某些 PDO 版本下会忽略 charset 设置,继续用默认 utf8 握手。
删掉 .env 里的 DB_CHARSET=utf8mb4 —— Laravel 官方配置根本不读这个变量,留着反而误导。
重启 PHP-FPM 和 Web 服务后,运行 php artisan tinker → DB::connection()->getPdo()->getAttribute(PDO::ATTR_CONNECTION_STATUS),确认返回含 utf8mb4 字样。
Redis 缓存序列化对齐
Cache::get() 返回 null 不一定是没命中,很可能是写入用 serialize()、读取用 msgpack,或者 phpredis 没开 serialize 选项,把对象存成了字符串。
方法一:确认 config/cache.php 中 redis store 的 serialize 选项已启用
'serialize' => true
方法二:若用 phpredis 扩展,必须确保 PHP 已加载它;若用 predis,则 config/database.php 中 'client' => 'predis' 必须与实际一致。
【client 类型必须与已安装扩展严格一致】 写成 'phpredis' 却没装扩展,会直接报 Class 'Redis' not found,而不是静默失败。
方法三:升级到 Laravel 13.2.0+ 后,数组和简单对象自动启用 msgpack,体积减少约 40%,但前提是 cache driver 是 redis 或 memcached —— file 或 database 驱动不支持二进制值。
验证缓存是否真正生效
第一步:清配置缓存
php artisan config:clear
第二步:手动写入并读取测试键
php artisan tinker --execute="Cache::store('redis')->put('charset_test', ['emoji' => '✅', '中文' => '你好'], 60); var_dump(Cache::store('redis')->get('charset_test'));"
第三步:检查输出是否完整包含 ✅ 和 你好 —— 如果 emoji 变成 ? 或中文变乱码,说明 MySQL 连接层没生效;如果返回 null 或字符串而非数组,说明 Redis 序列化没对齐。
第四步:用 redis-cli 直接查键内容
redis-cli -h $REDIS_HOST -p $REDIS_PORT -a "$REDIS_PASSWORD" get "laravel_cache:charset_test"
若返回二进制乱码,说明 msgpack 生效;若返回 PHP serialized 字符串(以 a:2:{... 开头),说明 serialize=true 生效;若返回空或报错,说明连接根本没通。











