MySQL中data->>'$.field'不走索引,因B+树索引仅识别物理列名,无法优化JSON路径表达式;必须创建STORED虚拟列并显式建索引,且查询须直接引用该列。

为什么data->>'$.field'查询不走索引
MySQL 的 B+ 树索引只认物理列名,不认运行时表达式。data->>'$.field'每次执行都要解析整段 JSON、提取字段、去引号、比较——优化器看不到“结构”,只能全表扫描。EXPLAIN 显示 type: ALL 就是铁证。哪怕表只有 1 万行,CPU 也会在 JSON 解析上空转;10 万行可能带来百 GB 级内存和 CPU 开销。
必须用 STORED 虚拟列 + ->> + 显式类型三者缺一不可
错一个,索引就白建:
-
->>返回标量值(如active),->返回带引号字符串(如"active");WHERE中写status = 'active'必须匹配前者 - 类型必须显式且紧凑:状态码用
VARCHAR(20),ID 用INT UNSIGNED,时间用DATETIME;别用TEXT或VARCHAR(255),InnoDB 可能截断前缀 - 优先选
STORED:值物理落地,行为稳定;VIRTUAL在子查询、UNION或 MySQL 8.0.13 前版本中可能退化为每次计算
正确示例:
将一个或多个视频上传至YouTube。当用户想“上传到YouTube”“发YouTube”“批量上传”“upload to YouTube”“post videos to YouTube”或发布到YouTube时使用此技能。
ALTER TABLE orders ADD COLUMN status VARCHAR(20) GENERATED ALWAYS AS (data->>'$.status') STORED, ADD INDEX idx_status (status);
查询语句必须改写,不能沿用旧写法
优化器不会自动把 data->>'$.status' 映射到新列。老 SQL 不改,索引等于没建:
- ✅ 走索引:
SELECT * FROM orders WHERE status = 'shipped'; - ❌ 不走索引(哪怕逻辑等价):
SELECT * FROM orders WHERE data->>'$.status' = 'shipped'; - ⚠️ 隐式转换废索引:虚拟列是
INT,但传入字符串'123';或末尾多空格'shipped ' - ⚠️ 函数包装必废索引:
WHERE UPPER(status) = 'SHIPPED'——虚拟列索引彻底失效
验证索引是否真生效,别只看 key 字段
很多人看到 key: idx_status 就以为索引生效了,但实际可能仍在全量解析 JSON。关键要看三处:
-
type必须是ref、range或const,不能是ALL或index -
rows应显著小于总行数(比如 10 万行表,rows: 120才算有效;若仍是rows: 98234,说明索引没被用于过滤) -
Extra里不能有Using filesort或Using temporary——这表示排序/分组仍触发 JSON 实时解析
最易被忽略的是:即使加了 STORED 虚拟列和索引,如果业务后期频繁查动态路径(如 $.items[2].name),就得新增列或重构;而函数索引虽灵活,但 8.0.17 之前不支持多值,数组场景仍得靠生成列兜底。


















