mysql 8.0 支持 json 字段局部更新,但需满足五项条件:列类型为 json;仅用 json_set/replace/remove 之一且不嵌套;路径指向已有位置;新值 utf-8 字节数≤原值;版本≥8.0.4。

MySQL 8.0 能对 JSON 字段做真正的局部更新——只要满足条件,InnoDB 就不会整列重写,而是只修改变更的字节段,减少 redo log、binlog 和磁盘 IO 开销。但这不是默认开启的魔法,得按规则来。
JSON_SET 和 JSON_REPLACE 的行为差异必须分清
这两个函数看着像,但底层逻辑和适用场景完全不同:
-
JSON_SET():不管键存不存在,都会设置(新增或覆盖)。比如JSON_SET(json_col, '$.status', 'done'),即使原 JSON 没有status字段,也会插入; -
JSON_REPLACE():只替换已存在的键,若路径不存在,操作静默失败(不报错,也不生效); - 想安全地“仅更新已有字段”,用
JSON_REPLACE();想“确保字段存在”,必须用JSON_SET(); - 误用
JSON_REPLACE()更新一个刚加的字段,会看似执行成功,实际值没变——查ROW_COUNT()返回 0 就能发现。
局部更新生效的硬性条件
哪怕用了 JSON_SET(),MySQL 也未必走局部更新路径。必须同时满足:
- 目标列类型是
JSON(不是TEXT或VARCHAR); - UPDATE 语句中只用
JSON_SET()、JSON_REPLACE()或JSON_REMOVE()之一,且只作用于该 JSON 列本身(不能嵌套其他表达式); - 所有更新路径都指向**已有对象或数组内的已有位置**,不能在对象里新增顶层 key(如
$.new_field),也不能往数组末尾追加($[99]超出当前长度); - 新值的 UTF-8 编码字节数 ≤ 原值(否则仍会整列重写);
- MySQL 8.0.4+ 才完整支持 LOB 局部更新机制,低于此版本基本等同于整列覆写。
避免踩坑的实操建议
局部更新不是“写了就生效”,日常开发中容易忽略这些细节:
- 更新前先用
JSON_EXTRACT(json_col, '$.key')检查字段是否存在,再决定用JSON_SET()还是JSON_REPLACE(); - 批量更新多个键时,优先用单次
JSON_SET(json_col, '$.a', 1, '$.b', 2),而不是多条 UPDATE——减少事务开销,也更易触发局部更新; - 更新后立即查
JSON_STORAGE_SIZE(json_col),对比更新前后是否变化不大,可验证是否真走了局部路径; - 别依赖
JSON_MERGE_PATCH()做局部更新——它内部会重建整个文档,不满足局部更新条件; - MyBatis 等 ORM 中,如果 TypeHandler 把 JSON 当字符串处理,或预编译参数被转义,会导致函数失效,必须显式配置
json类型映射。
局部更新真正起效的前提,是 MySQL 看得懂你“只动几个字节”的意图。路径写错、值超长、函数选错,任何一个环节出问题,InnoDB 都会退回到整列重写的老路——而这个退路完全静默,连慢日志都不会报异常。











