MySQL 5.7 中 JSON 列无法直接建索引,需通过 STORED 生成列(如 AS (payload >> '$.level'))映射并索引内部字段,否则 JSON_EXTRACT 等函数导致全表扫描;VIRTUAL 列在 5.7 不支持二级索引。

MySQL 5.7 的 JSON 查询本身不快,真正快的是你给它配了索引 —— 而原生 JSON 列不能直接建索引,必须靠 Generated Column(虚拟列或存储列)中转。
为什么直接对 JSON 列用 WHERE JSON_EXTRACT(...) 会慢
因为 JSON_EXTRACT(json_column, '$.status') 是一个计算型表达式,MySQL 无法在该表达式结果上复用已有的 B+ 树索引。即使 json_column 本身有索引,优化器也只会走全表扫描或索引全扫(取决于执行计划),尤其当数据量超过几万行时,EXPLAIN 里常看到 type: ALL 或 key: NULL。
- JSON 列本质是二进制存储的文档,不是普通字符串,
LIKE或函数包装后无法命中索引 -
JSON_CONTAINS、JSON_SEARCH等函数同样无法利用索引加速,除非它们的操作对象是已索引的生成列 - 5.7 不支持函数索引(8.0.13+ 才有
CREATE INDEX ... ON (JSON_EXTRACT(...))),所以必须“借道”生成列
怎么用 Virtual Generated Column 建索引
核心是两步:先加一列映射 JSON 内部字段,再给这列建索引。例如想按 $.user_id 快速查:
ALTER TABLE orders ADD COLUMN user_id INT AS (JSON_EXTRACT(data, '$.user_id')) STORED, ADD INDEX idx_user_id (user_id);
- 必须用
STORED——VIRTUAL列在 InnoDB 上不能建二级索引(5.7 限制),否则报错ERROR 3107 (HY000): Generated column can only be stored -
JSON_EXTRACT返回的是 JSON 类型,如果目标字段是整数,要包一层CAST(... AS UNSIGNED),否则索引值是带引号的字符串,导致数值比较失效 - 路径表达式必须字面量固定,不能含变量或函数,比如不能写
JSON_EXTRACT(data, CONCAT('$.', @key))
常见踩坑点:类型不匹配导致索引失效
这是最隐蔽也最高频的问题。例如:
DMap GL地图JSAPI(dmapgl)开发指南。用于编写、审查、调试 DMap 地图 API 代码,涵盖地图初始化、覆盖物渲染、图层管理、事件处理、控件交互等任务。当用户提及 dmap、dmapgl、jsapi‑gl 或相关地图开发需求时自动触发。
SELECT * FROM logs WHERE JSON_EXTRACT(payload, '$.level') = 'error';
看着合理,但如果你建的生成列是:
ADD COLUMN level VARCHAR(20) AS (JSON_EXTRACT(payload, '$.level')) STORED
—— 实际存进去的是 "error"(带双引号的 JSON 字符串),不是 error。所以上面查询永远走不了索引。
- 正确做法是用
->>操作符或JSON_UNQUOTE(JSON_EXTRACT(...))去引号:AS (payload ->> '$.level') - 或者显式转换:
AS (CAST(JSON_EXTRACT(payload, '$.level') AS CHAR)) - 验证方法:查一下生成列值
SELECT level, HEX(level) FROM logs LIMIT 1,如果 HEX 结果含22(即 ASCII 双引号),说明没去引号
Virtual vs Stored:选哪个?
5.7 下基本只能选 STORED,因为 VIRTUAL 列不支持建索引(除主键外)。但要注意代价:
-
STORED列会在每行物理存储一份计算结果,增加磁盘占用和写入开销(INSERT/UPDATE 时多一次计算 + 存储) - 一旦定义好,不能把
STORED改成VIRTUAL,也不能反过来;改类型必须DROP COLUMN再重建 - 如果只是偶尔查某个 JSON 字段,且数据量不大,其实不如用
JSON_CONTAINS+ 全文索引(ADD FULLTEXT(payload)),但全文索引只适合文本模糊匹配,不适用于精确等值查询
真正需要权衡的,不是 virtual 还是 stored,而是:这个字段值是否稳定、是否高频查询、磁盘和写入成本能否接受。别为了省几 MB 空间,换来每次查询都慢 200ms。

















