JSON_SET() 并非真正局部更新,需满足五项条件:列类型为JSON、单次仅一个JSON函数、路径指向已有位置、二进制差异≤100字节、ROW_FORMAT为DYNAMIC/COMPRESSED,否则仍触发全量重写。

直接用 JSON_SET() 更新 JSON 字段,不等于就真“局部更新”了——多数人执行完发现锁变多、慢查变多,其实是整列重写,根本没走优化路径。
JSON_SET() 会新增字段,但不保证局部更新
JSON_SET() 的行为很明确:路径不存在就创建,存在就覆盖。比如 JSON_SET(info, '$.status', 'done'),哪怕原 JSON 没有 status,也会插入成功。但它是否触发 MySQL 的“部分更新”机制,和函数本身无关,只取决于底层条件是否满足:
- 目标列必须是
JSON类型(不是VARCHAR或TEXT) - UPDATE 语句中只能出现一个 JSON 函数调用,且不能嵌套其他表达式(例如
CONCAT(JSON_SET(...), ...)就失效) - 所有路径必须指向已有对象/数组内的已有位置:不能在顶层加新 key(如
'$.new_field'),也不能往数组末尾追加(如'$[99]'超出当前长度) - 更新后二进制差异 ≤ 100 字节(注意是字节,不是字符;空格、引号、转义符都算)
为什么 UPDATE ... JSON_SET() 还是锁表严重?
常见现象:只改一个 $.price,却导致并发 UPDATE 频繁等待、慢日志里 innodb_n_rows_inserted 突增——说明 MySQL 没走原地修改,而是全量重写 LOB。
根本原因通常是这三项之一没达标:
详细的 Three.js 3D 图形参考,涵盖场景设置、相机、几何体、材质、光照、动画、控制器、加载器、数学工具和调试。
- 表的
ROW_FORMAT不是DYNAMIC或COMPRESSED(REDUNDANT和COMPACT不支持局部更新) - 更新前后 JSON 二进制差异超 100 字节(比如把
"old"改成"a_very_long_new_value_with_extra_quotes_and_escapes") - WHERE 条件没过滤掉 NULL 或非法 JSON 值(
JSON_SET(NULL, '$.x', 1)返回 NULL,整列被置空)
JSON_SET() 和 JSON_REPLACE() 别混用
这两个函数参数一样、名字像,但语义相反:
-
JSON_SET():强制设置,不管路径存不存在,都生效 -
JSON_REPLACE():只替换已存在路径,路径不存在就静默失败(ROW_COUNT()返回 0,但 SQL 不报错)
典型误用场景:想给老记录加个新字段,写了 JSON_REPLACE(info, '$.tags', '[]'),结果查出来还是空——因为 tags 原本就没这个 key。这时候必须换 JSON_SET(),或者先用 JSON_CONTAINS_PATH(info, 'one', '$.tags') 判断再分支处理。
生产环境建议加一层防护
光靠 JSON_SET() 不够稳妥,尤其字段可能为 NULL 或格式异常时:
- 初始化字段:更新前确保非 NULL,可用
IFNULL(info, '{}')包裹,避免JSON_SET(NULL, ...)导致整列变 NULL - 限制更新范围:配合
JSON_EXTRACT(info, '$.status') != 'done'做 WHERE 条件,避免无效更新浪费 IO - 验证是否局部生效:更新后查
INFORMATION_SCHEMA.INNODB_METRICS,比对innodb_n_rows_updated和innodb_n_rows_inserted + innodb_n_rows_deleted,后者显著更高就说明还在全量重写
真正起作用的从来不是函数名,而是那一组冷门但硬性的存储引擎约束。漏掉任意一条,JSON_SET() 就只是语法上“局部”,物理上仍是全量。

















