json查询未提速甚至变慢,根本原因在于索引未真正生效、json解析开销未规避、查询模式不匹配mysql 8.0优化边界,需验证explain执行计划、虚拟列定义与类型一致性、真实耗时及隐式校验陷阱。

升级后 JSON 查询没变快,甚至更慢——不是配置或SQL写错了,而是你还没验证索引是否真正生效、解析开销是否被规避、以及查询模式是否匹配 MySQL 8.0 的优化边界。
查 EXPLAIN 是否真走索引(而非“看起来走了”)
很多用户看到 key: idx_status 就以为索引生效了,但实际可能仍在全量解析 JSON。关键要看三处:
-
type必须是ref、range或const,不能是ALL或index -
rows应显著小于总行数(比如 10 万行表,rows: 120才算有效;若仍是rows: 98234,说明索引没被用于过滤) -
Extra里不能有Using filesort或Using temporary——这表示排序/分组仍触发 JSON 实时解析
确认虚拟列是否用 STORED + ->> + 显式类型
虚拟列建错一个细节,索引就白搭。常见失效组合:
- 用了
->而非->>:前者返回"active"(带引号字符串),后者返回active(标量),WHERE 中等值比较必须匹配后者 - 类型声明过大或不一致:比如 JSON 里
"status": "shipped",却定义成VARCHAR(255),InnoDB 可能截断索引前缀;应改用VARCHAR(20) - 用了
VIRTUAL而非STORED:尤其在子查询、UNION 或 MySQL 8.0.13 前版本中,VIRTUAL列可能退化为每次计算 - 漏掉
ON EMPTY NULL ON ERROR NULL(对JSON_TABLE)或未加JSON_VALID(json_col)条件,导致部分行无法命中索引路径
对比真实耗时,别只看逻辑读
EXPLAIN 的 rows 和 query_cost 是估算值,实际瓶颈常在 CPU 解析和内存拷贝。建议用以下方式实测:
- 用
BENCHMARK(1000, json_col->>'$.status')单独测单条 JSON 解析开销,确认字段平均长度与解析时间是否线性增长 - 开启
performance_schema,查events_statements_history_long中的LOCK_TIME、ROWS_AFFECTED和SQL_TEXT,定位高延迟 SQL 的真实执行阶段 - 对比升级前后同一查询的
Handler_read_next和Handler_read_rnd_next:前者飙升说明索引扫描效率低,后者飙升说明回表或随机读严重
检查是否踩中 MySQL 8.0 的隐式校验陷阱
8.0 默认启用更严格的 JSON 校验,哪怕已建索引,每次匹配仍会校验 UTF8MB4 编码合法性、路径是否存在、值类型是否匹配。这会导致:
- WHERE 条件中路径不存在(如
data->>'$.missing_field')时返回NULL,但若字段定义为NOT NULL,优化器可能放弃使用索引 - 隐式类型转换:虚拟列是
INT,但 WHERE 里传入'123'字符串,触发全表扫描 - ORDER BY 含 JSON 表达式(如
ORDER BY data->>'$.created_at')时,即使该路径有索引,8.0 默认启用hash_join和激进filesort,极易落盘
最易被忽略的一点:索引本身不解决 JSON 解析,只解决“找哪几行”。只要查询里还出现 data->>'$.xxx'、JSON_CONTAINS() 或 JSON_EXTRACT(),那一行数据在返回前仍要完整解析一次——STORED 虚拟列只是把这一步提前固化了,不是凭空消失。











