mysql 8.0的json字段不支持直接索引,data->>'$.key'必全表扫描;仅函数索引、stored虚拟列+b-tree索引、多值索引(数组)三类有效,且路径需预先确定、写法须严格匹配。

MySQL 8.0 的 JSON 字段本身不支持直接索引,data->>'$.key' 这类写法必然触发全表扫描(type: ALL),哪怕你给 data 列加了普通索引也完全无效。真正能提速的,只有三类明确落地的索引机制:函数索引、STORED 虚拟列 + B-Tree 索引、多值索引(仅限数组)。选错方案或写法稍有偏差,索引就等于没建。
为什么 WHERE data->>'$.status' 一定不走索引
MySQL 的 B+ 树索引只匹配物理列名,不识别运行时计算结果。data->>'$.status' 每次执行都要解析整段 JSON、剥离引号、提取字符串——这是纯 CPU 计算,优化器无法下推到索引层。EXPLAIN 里只要看到 type: ALL 和高 rows 值,基本就是这个原因。
- 别试
INDEX(data(10)):JSON 类型不支持前缀索引,语法报错或静默忽略 - 别信“建了 JSON 索引就完事”:MySQL 8.0 没有叫“JSON 索引”的独立类型,只有函数索引、多值索引等具体实现
-
->和->>返回类型不同:->返回带引号 JSON 值(如"active"),->>返回去引号标量(如active);建索引和查询必须类型一致
用函数索引(MySQL 8.0.13+,改动最小)
无需改表结构,直接在 JSON 路径表达式上建索引,但语法极其严格,少一个括号就失败。
- 必须用双括号:
ALTER TABLE orders ADD INDEX idx_status ((CAST(data->>'$.status' AS CHAR(20))));—— 外层(())缺一不可 - 类型必须显式且紧凑:状态用
CHAR(20),ID 用UNSIGNED,时间用DATETIME;用TEXT或超长VARCHAR可能导致索引截断 - 查询条件必须字面一致:
WHERE data->>'$.status' = 'shipped'才能命中;写成WHERE status = 'shipped'(假设你另建了虚拟列)则完全不匹配 - 隐式转换废索引:虚拟列是
INT,但传入字符串'123';或字段末尾有空格'shipped ',索引立即失效
用 STORED 虚拟列 + 普通索引(兼容性最强,推荐生产首选)
比函数索引更稳定,尤其适合 MySQL 8.0.13 之前版本或需要确定行为的场景。
- 必须用
STORED:GENERATED ALWAYS AS (data->>'$.status') STORED;VIRTUAL列不能建索引,且在子查询中可能退化为每次计算 - 表达式必须确定:禁止
RAND()、NOW()、子查询;允许JSON_UNQUOTE(JSON_EXTRACT(...)) - 字段名避开保留字:别起名
order、group、index,否则 DDL 报错 - 查询必须改写:建完后要查
WHERE status = 'shipped',而不是继续用data->>'$.status';老 SQL 不改,新索引毫无意义
处理 JSON 数组时用多值索引(ARRAY 类型专属)
仅适用于 data->'$[*].tag' 这类提取数组所有元素的场景,不是所有 JSON 数组都适用。
- 语法必须带
ARRAY:例如CAST(data->'$[*].tag' AS CHAR(32) ARRAY);漏掉ARRAY就变成单值索引,只取第一个元素 - 只加速
MEMBER OF()、CONTAINS()、JSON_CONTAINS()等数组语义查询,对=等值查询无效 - 嵌套数组需分层展开:比如
orders[*].items[*].skuid,得靠JSON_TABLE配合多值索引,不能指望一条索引覆盖全部层级 - 数组元素类型要统一:如果
tag有时是字符串、有时是数字,CAST会失败或静默转成 NULL,索引效果大打折扣
最易被忽略的一点:所有方案都要求你在写查询前就决定好路径。业务后期想查 $.items[2].name 或动态下标,函数索引和 STORED 列都得重建——没有“通用 JSON 索引”,只有针对明确路径的精确索引。











