json局部更新不改变行级锁粒度,仍对整行加记录锁;所谓“局部”仅指存储引擎是否重写整列二进制数据,与并发控制无关;锁范围扩大主因是where条件未走索引导致全表扫描。

局部更新不等于低锁开销
MySQL 的 JSON 局部更新(JSON_SET、JSON_REPLACE、JSON_REMOVE)本身不改变行级锁粒度——它仍会加记录锁(record lock),不是“只锁字段”。所谓“局部”仅指存储引擎层面是否重写整列二进制数据,和并发控制无关。你执行 UPDATE t SET j = JSON_SET(j, '$.status', 'done') WHERE id = 123,InnoDB 依然会对主键 id = 123 这一行加 X 锁,和其他普通 UPDATE 没区别。
WHERE 条件没走索引 → 全表扫描 + 全行加锁
真正放大锁冲突的,是 WHERE 条件无法命中索引,导致全表扫描。例如:
-
WHERE info->"$.notify_on_updates" = true:这个表达式无法利用生成列索引,优化器只能全表扫描,每行都加记录锁 - 正确写法必须显式使用生成列名:
WHERE notify_on_updates_flag = true(假设你定义了GENERATED ALWAYS AS (info->"$.notify_on_updates") STORED并建了索引) - 生成列不能是
VIRTUAL类型(不可索引),也不能含副作用函数(如NOW()),否则EXPLAIN显示type: ALL
JSON 函数本身不触发间隙锁,但范围条件会
单独用 JSON 路径做等值过滤(如 WHERE info->>'$.user_id' = 'U123')不会引入间隙锁(gap lock),前提是该列有合适索引;但如果 WHERE 里混用了范围条件(比如 AND created_at > '2026-06-01'),InnoDB 可能为避免幻读而加间隙锁——这和 JSON 无关,是普通二级索引的正常行为。
binlog partial update 开关影响的是复制效率,不是锁
MySQL 8.0.23+ 支持 binlog_row_value_options=PARTIAL_UPDATES,它只影响 binlog 记录方式(记录变更路径 vs 整列快照),对事务内锁行为零影响。别指望开了它就能减少锁冲突——锁由存储引擎在事务执行时决定,和日志怎么记无关。
最常被忽略的一点:即使满足所有局部更新条件(JSON 类型、单函数调用、路径存在、新值字节 ≤ 旧值),只要 WHERE 没走索引,锁范围就从“1 行”变成“N 行”,这才是生产环境锁等待飙升的主因。











