thinkphp升级后unserialize()报错,因6.x默认改用igbinary/msgpack驱动,而旧缓存存的是php原生serialize格式,导致反序列化时字节头不匹配而失败。

为什么 unserialize() 在 ThinkPHP 升级后直接报错?
因为新版 ThinkPHP(尤其是 6.x)默认改用 igbinary 或 msgpack 序列化驱动,而旧缓存(如 Redis、File)里存的是 PHP 原生 serialize() 格式。PHP 尝试用新驱动反序列化旧数据时,字节头不匹配,直接抛出 unserialize(): Error at offset 或静默失败。
- 典型现象:升级后首页白屏、登录态丢失、配置读取为空,日志里反复出现
Notice: unserialize(): Error at offset - 不是所有缓存都出问题——只有含对象、闭包或自定义类实例的缓存项会崩;纯数组/字符串可能侥幸通过
- Redis 缓存最危险:它不校验序列化协议,写入和读取用不同驱动时完全不报错,只返回
false或空值
如何让新版本兼容读取旧 serialize() 数据?
核心思路是「降级读取」:在反序列化前检测数据头,自动切换回 PHP 原生方式。ThinkPHP 6.1+ 提供了 SerializerInterface 扩展点,但别自己重写整个驱动——直接复用框架内置的 PhpSerializer 更稳妥。
- 在
app/provider.php中注册自适应反序列化器:
use think\cache\driver\Redis;
use think\cache\serializer\PhpSerializer;
return [
'cache' => [
'default' => 'redis',
'stores' => [
'redis' => [
'type' => 'redis',
'host' => '127.0.0.1',
'port' => 6379,
// 关键:强制使用 PhpSerializer,跳过 igbinary
'serializer' => PhpSerializer::class,
],
],
],
];
- 如果用了 File 缓存,确认
cache_path下的文件没被自动清理——旧缓存文件名带时间戳,升级后新版本可能忽略它们 - 不要全局禁用
igbinary扩展(如注释extension=igbinary.so),否则会影响其他依赖它的组件
升级后要不要立刻清空所有缓存?
要清,但不能粗暴 php think cache:clear —— 它只清当前驱动可识别的数据,对旧格式缓存项无效,反而留下“半死不活”的脏数据。
- Redis 场景:用
redis-cli -h 127.0.0.1 KEYS "think:*"扫描键,再用GET抽样检查值是否以a:(PHP serialize 数组头)或O:(对象头)开头;确认后执行FLUSHDB - File 场景:直接删掉
runtime/cache/下所有子目录,保留.gitignore和index.html即可 - 切记:清缓存前先停服务或切流量,避免用户请求触发旧缓存写入新格式,造成混合污染
长期方案:怎么避免下次升级再踩坑?
靠手动切序列化器只是过渡,真正要解决的是「缓存协议显式化」——让序列化方式成为可配置、可追踪、可灰度的选项,而不是隐式绑定在驱动上。
- 在缓存 key 前缀里嵌入序列化标识,比如把
user:123改成v2:php:user:123,升级时按前缀分流处理 - 业务关键缓存(如权限、配置)改用 JSON 存储:
json_encode($data, JSON_UNESCAPED_UNICODE),彻底避开序列化兼容问题 - 所有自定义缓存操作必须走封装层,禁止直接调
Cache::get(),统一拦截并打日志记录序列化方式,方便事后审计
跨版本缓存迁移最麻烦的从来不是技术方案,而是你永远不知道哪些角落还躺着三年前某次临时调试写进去的 serialize(new \stdClass()) —— 检查时多看两眼 runtime/log/ 里的反序列化警告,比写十行修复代码管用。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










