data->>'$.status'不走索引,因mysql b+树只识别物理列名而非运行时json表达式;须建stored虚拟列+显式类型+->>操作符三者缺一不可。

为什么 data->>'$.status' 查询不走索引
MySQL 的 B+ 树索引只认物理列名,不认运行时表达式。data->>'$.status' 每次执行都要解析整条 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 前版本中可能退化为每次计算 - 字段名避开保留字,比如别叫
order、group
正确示例:
ALTER TABLE orders ADD COLUMN status VARCHAR(20) GENERATED ALWAYS AS (data->>'$.status') STORED, ADD INDEX idx_status (status);
查询时必须改写 WHERE 条件,不能沿用旧写法
优化器不会自动把 data->>'$.status' 映射到新列。老 SQL 不改,索引等于没建:
- ✅ 走索引:
SELECT * FROM orders WHERE status = 'shipped'; - ❌ 不走索引(哪怕逻辑等价):
SELECT * FROM orders WHERE data->>'$.status' = 'shipped'; - ⚠️ 隐式转换废索引:虚拟列是
INT,但传入字符串'123';或末尾多空格'shipped ' - ⚠️ 函数包装必废索引:
WHERE UPPER(status) = 'SHIPPED'—— 虚拟列索引彻底失效
MySQL 8.0.13+ 可用函数索引,但语法和限制更严
想省表结构变更,可用函数索引,但必须满足硬性条件:
- 语法必须双括号:
CREATE INDEX idx_email ON t ((properties->>'$.email'));少一层括号就报错ERROR 3105 - 表达式必须确定:
->>和JSON_EXTRACT()合法,NOW()或子查询不行 - 数组路径需额外处理:
courses->'$[*].course_id'必须配合CAST(... AS CHAR(50) ARRAY),否则无法建多值索引 - 函数索引不支持
ON EMPTY/ON ERROR行为定义,出错直接中断;而STORED列可设默认值或允许NULL
真正要盯住的,不是“能不能建”,而是“查的时候有没有严格对齐索引定义”——路径、类型、操作符、括号层级,差一点,全表扫。











