JSON字段更新会触发整行重写,因InnoDB在结构变化(如新增键、改数组长度)时放弃局部更新,强制全量序列化,引发全行锁、undo日志暴涨及主从延迟;虚拟列索引无法规避更新开销,高频更新应拆表或换用文档数据库。

JSON字段更新会触发整行重写
MySQL的JSON类型虽支持局部修改,但仅限于JSON_SET、JSON_REPLACE等函数在满足特定条件时生效。一旦更新涉及新增键、改变嵌套结构深度、或修改数组长度(比如JSON_ARRAY_APPEND往空数组加元素),InnoDB就会放弃局部更新逻辑,转而将整个JSON值序列化为新二进制块,并重写整行数据。
这意味着:即使只改$.status一个字段,只要JSON内部结构变化超出优化器判断范围,就会带来全行锁、页分裂、undo日志暴涨、buffer pool污染等一系列连锁反应。
虚拟列索引无法避免UPDATE时的开销
很多人以为加了虚拟列索引就能“一劳永逸”,其实不然。虚拟列本身是计算出来的,它的值依赖原始JSON字段。每次UPDATE JSON字段,所有依赖它的虚拟列都必须重新计算并写入——哪怕你根本没查这个虚拟列。
常见错误场景包括:
- 建了
status VARCHAR(20) GENERATED ALWAYS AS (data->>'$.status') STORED,但业务频繁用JSON_SET(data, '$.updated_at', NOW())更新时间戳,导致status列无意义地反复计算 - 多个虚拟列指向同一JSON路径的不同cast方式(比如同时存
CAST(data->>'$.score' AS SIGNED)和CAST(data->>'$.score' AS DECIMAL(5,2))),一次更新引发多次解析
事务日志和复制延迟被隐式放大
JSON字段越大,每次变更产生的binlog事件体积就越大。尤其在ROW格式下,MySQL会记录完整的旧值+新值,而不是diff。实测发现:一个5KB的JSON字段更新,binlog写入量可能达12KB以上。
这直接拖慢主从同步,还容易触发max_binlog_size切割、网络传输缓冲区溢出等问题。更隐蔽的是:如果该表还启用了GTID或组复制,大JSON更新会显著抬高事务冲突概率和apply队列积压。
别忽略JSON解析本身的CPU成本
每次UPDATE都要对JSON做语法校验+二进制重构,这个过程不走索引、不缓存中间结果,纯CPU密集型操作。当并发更新量上来后,很容易看到SHOW PROCESSLIST里大量线程卡在executing状态,SHOW ENGINE INNODB STATUS中ROW OPERATIONS计数飙升。
特别注意:JSON_MERGE_PATCH这类函数比JSON_SET更耗资源,因为它要深度合并对象层级,实际性能可能比直接拼字符串还差。



















