MySQL 8.0+ 中 JSON 字段 WHERE 查询无法走索引,须用 VIRTUAL 生成列 + CAST 显式转类型建索引,或 8.0.13+ 函数索引;JSON_CONTAINS/JSON_OVERLAPS 和直接表达式索引均不支持路径索引。

MySQL 8.0+ 中 JSON 字段查得慢?先看是否建了 GENERATED COLUMN + INDEX
直接结论:原生 JSON 字段上用 JSON_EXTRACT() 或 -> 操作符做 WHERE 查询,**默认无法走索引**,哪怕字段本身是 JSON 类型。性能差的根源往往不是 JSON 解析慢,而是全表扫描。
真正可行的加速路径只有一条:把要查的路径“落地”为虚拟生成列,再在该列上建普通索引。
-
GENERATED COLUMN必须是STORED或VIRTUAL,但只有VIRTUAL不占存储、语义清晰,推荐用它 - 生成列表达式必须是确定性的,
JSON_EXTRACT(data, '$.status')合法,NOW()或子查询不行 - 类型要显式
CAST,比如CAST(JSON_EXTRACT(data, '$.status') AS CHAR(20)),否则索引可能失效或比较行为异常 - MySQL 5.7 不支持在
VIRTUAL列上建索引,必须升到 8.0+
ALTER TABLE orders
ADD status_virt VARCHAR(20)
GENERATED ALWAYS AS (CAST(JSON_EXTRACT(data, '$.status') AS CHAR(20))) VIRTUAL,
ADD INDEX idx_status_virt (status_virt);
为什么不用 JSON_CONTAINS() 或 JSON_OVERLAPS() 直接查?
这些函数语义清晰,但**绝大多数场景下无法命中索引**——它们作用于整个 JSON 文本,优化器无法将路径映射到已知有序结构。
典型误用:
SELECT * FROM orders WHERE JSON_CONTAINS(data, '"shipped"', '$.status');
这会触发全表扫描,即使 data 字段很小。除非你查的是 JSON 数组顶层成员且用了 JSON_CONTAINS + JSON_ARRAY 构造,否则别指望它快。
-
JSON_CONTAINS适合判断某个值是否在 JSON 数组里(如["a","b","c"]),不是用来查对象字段的 -
JSON_OVERLAPS是 8.0.17+ 新增,逻辑更严格,但同样不解决路径索引问题 - 如果硬要用,至少确保被查字段有
JSON类型约束(CHECK (data IS JSON)),否则解析失败会静默跳过行
JSON 索引本身(CREATE INDEX ... ON ... (data->"$.field"))到底支不支持?
MySQL **不支持直接对表达式(如 data->"$.field")建二级索引**。下面这条语句会报错:
使用约定式提交(Conventional Commits)生成标准化的 Git 提交信息。适用于以下场景: (1) 暂存更改时需要提交信息; (2) 审查 diff 时希望获得结构化摘要; (3) 编写 PR 描述或变更日志(changelog)条目; (4) 用户明确要求“为本次更改编写提交信息”。 该功能会分析已暂存/未暂存的 diff,并生成按类型(type)划分范围的提交信息。
CREATE INDEX idx_json_status ON orders (data->"$.status"); -- ❌ Syntax error
但你可以建一个「函数索引」——仅限 MySQL 8.0.13+,且要求表达式是「函数索引允许的确定性函数」:
CREATE INDEX idx_json_status ON orders ((CAST(data->"$.status" AS CHAR(20))));
注意括号:外层双括号 ((...)) 是函数索引语法必需;CAST 不可省,否则类型推导失败。
- 函数索引和虚拟列+索引效果等价,但可读性差、调试困难,线上建议优先用虚拟列
- 函数索引不能用于
ORDER BY排序优化,而虚拟列可以 - 如果 JSON 路径返回
NULL,该行不会进入索引(B+Tree 不存 NULL),查IS NULL仍需全表扫
容易被忽略的坑:collation、大小写、空格与 JSON 格式校验
虚拟列或函数索引建好后还是慢?大概率栽在这几个细节上:
-
JSON_EXTRACT()返回带引号的字符串(如"shipped"),而你查的是shipped(无引号),匹配不上 -
CAST(... AS CHAR)默认用表级collation,若为utf8mb4_0900_as_cs(区分大小写),"Shipped"≠"shipped" - JSON 字段里混入多余空格或换行(如人工编辑、前端未
JSON.stringify()),导致JSON_VALID(data)为FALSE,生成列值为NULL - 没加
CHECK (JSON_VALID(data))约束,坏数据悄悄入库,查的时候部分行消失,排查极难
上线前务必跑一遍验证:
SELECT COUNT(*) FROM orders WHERE data IS NULL OR NOT JSON_VALID(data);
复杂点永远在数据质量与类型边界,不在语法本身。










