MySQL 5.7 的 JSON 查询必须全表扫描,因其不支持函数索引,无法为 data->>'$.status' 等表达式直接建索引;只能通过 STORED 生成列+普通索引间接加速,且查询必须引用该列才能命中索引。

MySQL 5.7 实际上从 5.7.8 版本起就原生支持 JSON 类型,不是“默认不支持”,而是支持得有限——它能存、能校验、能提取,但无法高效查、无法局部改、无法直接索引路径。所谓“8.0 中可以高效使用”,核心在于它补上了这三块关键能力。
为什么 5.7 的 JSON 查询总是全表扫描?
因为 WHERE data->>'$.status' 这类表达式在 5.7 中无法被索引。优化器根本不识别该表达式可映射到某个有序值序列,只能逐行解析 JSON 字符串再比对。即使字段是 JSON 类型,也毫无加速作用。
- 你不能直接在
JSON_EXTRACT(data, '$.status')上建索引,会报错ERROR 3105 - 必须绕道:先定义
STORED生成列(如status VARCHAR(20) AS (JSON_UNQUOTE(data->>'$.status')) STORED),再对该列建普通索引 -
VIRTUAL列在 5.7 中不可索引,建了也白建,EXPLAIN显示key: NULL - 查询语句必须写成
WHERE status = 'active',若仍用data->>'$.status',索引完全不会被选中
8.0 的函数索引为什么能真正生效?
MySQL 8.0 允许在表达式上直接建 B+ 树索引,语法是双层括号:CREATE INDEX idx_status ON t ((data->>'$.status'))。这不是语法糖,而是优化器层面的重构——它把表达式结果确定性地物化进索引页,且不额外占用表空间。
使用 JSON Schema 验证 JSON 数据,从示例 JSON 生成 schema,并将其转换为 TypeScript 接口、Python 数据类或 Markdown 文档。
- 索引内容就是每个
data字段解析后$.status的字符串值,WHERE data->>'$.status' = 'active'能命中type: ref - 表达式必须是
DETERMINISTIC,JSON_EXTRACT()和->>满足,但NOW()、RAND()不行 - 路径过长(如
'$.metadata.tags[*].name')可能被截断,导致部分匹配失效,需检查innodb_page_size和innodb_large_prefix - 升级后别忘了删掉 5.7 留下的
STORED列——它们已无存在必要,反而拖慢INSERT/UPDATE
为什么 5.7 更新 JSON 总是重写整字段?
5.7 没有 JSON 部分更新能力。每次调用 JSON_SET(data, '$.name', 'Alice'),MySQL 都要把整个 JSON 文本反序列化、修改、再序列化回二进制格式,然后全量写入磁盘。高并发下容易锁表、放大 binlog、触发主从延迟。
- 8.0.27+ 支持真正的
JSON_PARTIAL_UPDATE,底层可只修改内部节点,避免整字段重写 -
JSON_REPLACE()只改已有键,JSON_SET()可新增或覆盖,JSON_INSERT()仅插入不覆盖——语义更清晰,执行更轻量 - 若业务频繁更新 JSON 内部字段(如订单状态、设备上报值),5.7 的写放大问题会随数据量指数级暴露
真正容易被忽略的,不是“能不能用 JSON”,而是 5.7 中所有索引加速都依赖人工物化路径,且对字符集、表达式字面量、查询写法极度敏感;8.0 虽简化了索引路径,但函数索引一旦写法稍有偏差(比如多一个空格、漏一层括号),就会静默退化为全表扫描。

















