MySQL 8.0 的 JSON 查询性能提升源于索引支持与二进制存储优化:支持函数索引(如 CREATE INDEX idx ON t ((data->>'$.status'))),避免全表扫描;JSON_TABLE() 实现嵌套结构下推;二进制格式使部分更新和字段提取更高效。

MySQL 8.0 的 JSON 查询不是“变快了”,而是终于能走索引、真正下推、避免重复解析——5.7 里那些看似能用的 ->> 写法,绝大多数情况下都在全表扫描。
为什么 WHERE data->>'$.status' = 'active' 在 5.7 中不走索引
5.7 完全不支持函数索引,data->>'$.status' 是运行时表达式,B+ 树索引无法匹配。你必须绕路:先建 STORED 生成列,再在该列上建索引,且表达式必须字面一致(JSON_UNQUOTE(JSON_EXTRACT(data, '$.status')) 和 data->>'$.status' 被视为不同表达式,索引不会命中)。
- 生成列必须是
STORED,VIRTUAL列不可索引 - 查询条件必须写成
WHERE status_col = 'active',不能写回原始表达式 - 任何额外包装,比如
UPPER(data->>'$.status'),直接让索引失效 - 每次
->>都会重新解析整段 JSON 文本,即使只取一个字段
CREATE INDEX idx ON t ((data->>'$.status')) 在 8.0 中真实生效
8.0 的双括号语法是函数索引的明确标识,它会在 B+ 树中存储解析后的字符串值,而非原始 JSON blob。执行计划里能看到 type: ref、key: idx,不再是 type: all。
- 无需生成列,无额外存储开销
- 支持多字段组合,如
((data->>'$.env'), (data->>'$.region')) - 仅限确定性表达式:
JSON_EXTRACT()可以,NOW()或RAND()不行 - 索引长度仍受
innodb_large_prefix限制(默认 3072 字节)
JSON_TABLE() 让嵌套数组可参与 JOIN 和 WHERE 下推
5.7 的 JSON_EXTRACT() 是纯计算函数,无法把 WHERE jt.id > 100 下推到解析层;8.0 的 JSON_TABLE() 在执行计划阶段就完成结构展开,输出标准虚拟表。
- 必须显式声明
ON EMPTY NULL ON ERROR NULL,否则某条记录字段类型不匹配(如字符串"1001"vs INT),整行丢弃 - 嵌套超过 3 层(如
orders → items → options → price)时,NESTED PATH解析开销明显上升 - 搭配函数索引,可替代部分 ETL 场景,比如实时聚合日志中的
event_data.items
二进制存储和部分更新带来的实际收益
8.0 的 JSON 不再是文本解析,而是带偏移和类型标记的紧凑二进制格式;JSON_SET() 等操作可只修改 LOB 中对应片段,不重写整个字段。
-
JSON_EXTRACT(data, '$.user.name')直接跳转目标位置,接近 O(1) 时间复杂度 - 高并发更新单个 key(如
$.status)时,锁粒度更细、binlog 更小 - 但注意:8.0 默认更严格的 UTF8MB4 校验 + 代理对检查,单次解析开销略高——所以没索引时反而可能比 5.7 慢
最容易被忽略的是:所有这些优势都依赖你**用对语法**。写错一个括号、漏掉 STORED、在 WHERE 里继续用原始表达式、或滥用 JSON_CONTAINS() 做等值查询——8.0 的 JSON 可能比 VARCHAR 还慢。









