mysql 8.0 中 json 字段查询无法直接走索引,需用 stored 生成列+普通索引;函数索引虽可行但对语法和字符集敏感,collate 不一致或查询未严格匹配将导致索引失效。

直接查 JSON_EXTRACT() 或 JSON_CONTAINS() 的 WHERE 条件,MySQL 8.0 不会走索引——这不是 bug,是设计使然。 你得把路径值物化出来,再建普通索引,且查询语句必须改写为查那个物化列。
为什么 JSON_EXTRACT(data, '$.status') 上加索引没用
MySQL 的 B+ 树索引只认确定、可排序的标量值。JSON_EXTRACT() 是运行时函数调用,优化器无法预判其输出分布,只能全表扫描。即使你建了函数索引:CREATE INDEX idx ON t((JSON_EXTRACT(data, '$.status'))),也要求查询中字面量**完全一致**:括号不能少、嵌套顺序不能变、连空格都不能多。稍有偏差就失效。
最稳方案:用 STORED 生成列 + 普通索引
适用于读多写少、路径固定的场景。它把值物理存下来,和普通字段无异,索引稳定、排查简单。
ALTER TABLE t ADD COLUMN status VARCHAR(20) AS (JSON_UNQUOTE(JSON_EXTRACT(data, '$.status'))) STORED;CREATE INDEX idx_status ON t(status);- 查询必须写成
WHERE status = 'active',不能写WHERE JSON_EXTRACT(data, '$.status') = 'active' - 若原路径可能为空或不存在,
JSON_EXTRACT()返回NULL,JSON_UNQUOTE(NULL)仍是NULL,所以WHERE status IS NULL也能走索引(MySQL 8.0+ 支持)
字符集不一致会让索引彻底失效
这是最隐蔽的坑:生成列定义没显式指定 COLLATE,而源 JSON 字段是 utf8mb4_bin,导致生成列默认用了 utf8mb4_0900_as_cs。比较时触发隐式转换,索引直接被跳过。
- 查当前列排序规则:
SHOW FULL COLUMNS FROM t LIKE 'status';看Collation列 - 建列时强制对齐:
AS (JSON_UNQUOTE(...)) STORED COLLATE utf8mb4_bin - 或统一用业务推荐的
utf8mb4_0900_ai_ci(更宽松,大小写不敏感)
函数索引能省一列但限制极多
如果你不想改表结构、又确定只查这个路径,可用函数索引。但它不支持 JSON_CONTAINS() 这类返回布尔的函数直接建索引,且对写法极其敏感。
- 正确语法:
CREATE INDEX idx_func ON t((JSON_UNQUOTE(JSON_EXTRACT(data, '$.status'))));(注意外层括号) - 查询必须严格匹配:
WHERE JSON_UNQUOTE(JSON_EXTRACT(data, '$.status')) = 'active' - 一旦 WHERE 中混入变量、参数绑定、或任何额外函数包装,索引立即失效
真正容易被忽略的,不是“要不要建索引”,而是生成列的 COLLATE 是否与查询条件一致、以及查询语句是否真的在用那个新列——哪怕只漏掉一个 UNQUOTE() 或写错一个引号位置,索引就形同虚设。











