mysql对json字段的where条件无法使用生成列索引,必须显式引用stored类型虚拟列且避免排序规则冲突,否则将触发全表扫描和x锁争用。

WHERE条件用JSON路径表达式会全表扫描加锁
直接写 WHERE preferences->"$.notify_on_updates" = true 看似合理,但MySQL无法用生成列索引加速这个条件——优化器根本不会走索引,而是全表扫描每行并加X锁。哪怕你建了 notify_on_updates_flag 这个虚拟列并加了索引,只要WHERE里没显式引用它,就等于没建。
- 必须把条件改成
WHERE notify_on_updates_flag = true,且确保该列是STORED类型(VIRTUAL不可索引) - 生成列表达式里不能含
NOW()、UUID()等副作用函数,否则索引失效 - 用
EXPLAIN FORMAT=JSON查看key字段是否命中索引,rows是否为1;若仍是"access_type": "ALL",说明锁范围没缩窄
JSON字段更新本身不触发额外锁,但WHERE没索引会放大争用
JSON_SET 或 JSON_REPLACE 本身只是行内修改,不改变主键或索引列值时,不会引发页分裂或二级索引重建。真正拖慢并发的是WHERE条件没走索引导致的锁范围失控。
- 高频更新同一JSON字段(如用户通知开关)时,如果WHERE只靠
JSON_EXTRACT判断,极易退化为临键锁甚至表锁 - 检查
information_schema.INNODB_TRX中trx_state = 'LOCK WAIT'的事务数,配合INNODB_LOCK_WAITS定位持锁者 - 避免在WHERE里对JSON字段做函数运算,比如
WHERE JSON_CONTAINS(preferences, '"email"')无法走索引,应改用预计算的布尔标志列
高并发更新同一行JSON字段时,X锁排队无法绕过
即使WHERE命中单行、索引完美、生成列也建对了,只要多个请求都打在同一主键上(比如 UPDATE users SET preferences = JSON_SET(...) WHERE id = 123),InnoDB仍会让所有事务串行获取X锁——这是聚簇索引的物理限制,不是配置能调的。
- 分片更新(shard by slot)有效,但前提是:联合主键包含分片键(如
(user_id, shard_id)),且UPDATE语句中shard_id必须出现在WHERE里 - 事务体必须极简:只含一条UPDATE和
ROW_COUNT()判断,禁用日志、HTTP调用、SLEEP - 隔离级别设为
READ COMMITTED,防止REPEATABLE READ下间隙锁扩大锁范围
字符集/排序规则不匹配让JSON虚拟列索引失效
虚拟列 event_type VARCHAR(32) AS (JSON_UNQUOTE(JSON_EXTRACT(...))) VIRTUAL 建好了,但查询 WHERE event_type = 'page_view' 还是慢?大概率是排序规则冲突:JSON_EXTRACT 返回值默认带 utf8mb4_bin 规则,而你的字符串字面量用的是 utf8mb4_0900_ai_ci,隐式转换导致索引失效。
- 显式声明虚拟列排序规则:
event_type VARCHAR(32) COLLATE utf8mb4_0900_ai_ci AS (...) - 或统一用二进制比较:
WHERE event_type COLLATE utf8mb4_bin = 'page_view' - 用
SHOW CREATE TABLE确认虚拟列实际使用的COLLATION,别信建表语句里没写的默认值










