Magento 老版本和一些扩展曾把 PHP serialize 字符串写进数据库,新版本改用 JSON 反序列化后,读取旧值就会报 Unable to unserialize value 或 Syntax error。最危险的“修复”是直接改 vendor/magento/framework/Serialize/Serializer/Json.php,让它失败后自动尝试 PHP unserialize()。这样会把脏数据问题扩散到整个系统,也让后续升级更难判断。

先从堆栈找到是谁在读这段值

异常最后落在 Json.php 不代表根因在那里。从调用栈向上找第一个具体模块、配置路径或数据模型。例如错误只在编辑客户时出现,就优先查客户扩展属性;只在结账页出现,则查支付、配送或营销模块读取的配置和缓存。

临时调试可以在调用方记录字段主键和原始值长度,但不要把客户数据直接写入日志。拿到疑似值后分别验证:

php -r '$v=$argv[1]; json_decode($v, true); var_dump(json_last_error_msg());' '待检查内容'

PHP 序列化字符串常以 a:、s:、i:、O: 开头,但不能只靠前缀批量判断,更不能对不可信内容使用允许对象实例化的 unserialize()。

确认字段后,先统计再迁移

假设已定位到某扩展表的 options 字段,先查总量、空值和样本:

SELECT COUNT(*) AS total,
       SUM(options IS NULL OR options = '') AS empty_values
FROM vendor_module_item;

SELECT entity_id, LEFT(options, 120)
FROM vendor_module_item
WHERE options IS NOT NULL AND options <> ''
LIMIT 20;

不要因为某个回答说“truncate 这张表”就照做。表中可能同时保存业务状态或订阅关系。最稳妥的是由扩展升级脚本逐行读取旧格式,使用受限的 unserialize($value, ['allowed_classes' => false]) 转成数组,再编码为 JSON,并记录成功、跳过和失败数量。

数据迁移要能停、能重跑、能核对

先备份目标表;按主键分批处理;每批提交;转换前后保留行数和校验信息。遇到无法解析的值不要写成空数组掩盖问题,而是把主键放进失败清单,确认它究竟是截断数据、双重序列化还是扩展版本不匹配。

修完数据再清相关缓存

如果错误值来自 core_config_data,清 config cache;来自模块缓存,则清对应 cache type。不要把“全站 cache:flush”当成数据修复。最终要重复触发原页面或命令,并在日志中确认没有新的反序列化异常。正确结果是数据库里的旧格式被一次性迁移,而不是框架从此默默兼容所有坏值。