JSON_SET()可新增或覆盖路径值,自动补全缺失父级;JSON_REPLACE()仅更新已存在路径,路径缺失则静默失败。

直接用 JSON_SET(),路径写对、别漏引号,90% 的嵌套更新问题就解决了。
为什么不用 JSON_REPLACE() 更新嵌套字段
它只改已有路径,一旦嵌套层级里某一级 key 不存在(比如 $.address.city 中 address 是 null 或压根没这个字段),整个操作就静默失败——查不到报错,也看不到效果,容易误以为“更新成功”了。
而 JSON_SET() 会自动补全中间缺失的 object,更符合实际业务中“确保字段存在并设值”的意图。例如:
-
JSON_SET(info, '$.address.city', 'Shanghai'):即使info原来是{"name": "张三"},执行后也会变成{"name": "张三", "address": {"city": "Shanghai"}} -
JSON_REPLACE(info, '$.address.city', 'Shanghai'):同样输入,结果还是原样,address不会出现
JSON_SET() 路径写法的三个易错点
嵌套越深,路径越容易写错,常见坑集中在引号、下标、特殊字符上:
- 键名含空格或横线(如
home-address)必须用双引号包裹:'$."home-address".postcode',写成$.home-address.postcode会报语法错误 - 数组下标从 0 开始,且必须用方括号:
'$.orders[0].items[1].sku',写成[1]但忘了前面的[0]就可能改错订单 - 路径开头的
$不能漏,也不能多写——'$.profile.settings.theme'对,'$$.profile.settings.theme'错
更新 JSON 数组中特定元素(按内容匹配)
如果要改的不是“第几个”,而是“某个字段等于 X 的那一个”,就不能靠固定下标了。MySQL 本身不支持在 UPDATE 中直接做数组内容匹配更新,得借力子查询 + JSON_TABLE 定位索引:
比如用户 roles 是个数组:[{"id": 101, "name": "admin"}, {"id": 202, "name": "editor"}],想把 id = 202 的 name 改成 "lead":
UPDATE users u
JOIN (
SELECT user_id, idx - 1 AS pos
FROM users u2,
JSON_TABLE(
u2.data, '$.roles[*]' COLUMNS (
id INT PATH '$.id',
idx FOR ORDINALITY
)
) AS jt
WHERE jt.id = 202
) t ON u.id = t.user_id
SET u.data = JSON_SET(u.data, CONCAT('$.roles[', t.pos, '].name'), '"lead"');注意:JSON_TABLE 的 FOR ORDINALITY 返回的是从 1 开始的序号,所以要减 1 才能对上 JSON 路径的 0 起始下标。
性能与并发要注意什么
每次 JSON_SET() 都会重写整条 JSON 字段的二进制内容,不是“局部 patch”。这意味着:
- 字段越大,UPDATE 越慢;若 JSON 超过 1MB,延迟会明显上升
- 高并发写同一行时,容易触发行锁争用——特别是多个请求都在改同一个 JSON 列的不同路径
- 没有真正意义上的“原子性字段更新”,
JSON_SET(a, '$.x', 1), '$.y', 2)是一次操作,但无法保证 x 和 y 同时生效或同时失败(除非整个语句回滚)
真正关键的嵌套字段,尤其是高频读写的(比如库存、状态),建议拆到独立列,而不是死守 JSON 存储。JSON 适合低频、结构不定、查询少的配置类数据。


















