mysql 8.0 json查询变慢主因是未物化路径值建索引,须用stored生成列+显式索引并改写查询语句;函数索引限制多且需字面量完全匹配;字符集不一致会导致索引失效。

MySQL 8.0升级后JSON字段查询变慢,不是JSON本身变慢了,而是你还在用5.7那套写法——直接在JSON_EXTRACT()或JSON_CONTAINS()上查,没走索引。必须把路径值物化成生成列并显式建索引,且查询语句要改写为查该列。
为什么直接查JSON路径不走索引
MySQL的B+树索引只认确定性、可排序的标量值。JSON_EXTRACT(data, '$.status')是运行时函数调用,优化器无法预判其结果分布,只能全表扫描。哪怕你加了CREATE INDEX idx ON t((JSON_EXTRACT(data, '$.status')))(函数索引),也要求查询中**字面量完全一致**:括号不能少、函数嵌套顺序不能变、连空格都不能多。
创建STORED生成列 + 普通索引是最稳的方案
对读多写少、查询路径固定的场景,优先用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路径可能为空或不存在,
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(推荐业务字段用此规则)
函数索引(MySQL 8.0.13+)能省一列但限制极多
如果你不想改表结构、又确定只查这个路径,可用函数索引。但它对写法极其敏感,且不支持JSON_CONTAINS()这类返回布尔的函数直接建索引。
- 正确语法:
CREATE INDEX idx_func ON t((JSON_UNQUOTE(JSON_EXTRACT(data, '$.status'))));—— 注意外层括号 - 查询必须严格匹配:
WHERE JSON_UNQUOTE(JSON_EXTRACT(data, '$.status')) = 'active' -
JSON_CONTAINS(data, '"admin"', '$.roles')不能直接建函数索引;得先建role_admin TINYINT AS (JSON_CONTAINS(...)) STORED再索引该列 - 函数索引不支持
LIKE、IN等范围操作,仅适用于等值查询
真正卡住性能的往往不是技术选型,而是“以为建了生成列就自动生效”——忘了STORED关键字,或忘了在SQL里切换成查新列。每次改完,务必用EXPLAIN确认key列显示的是你建的索引名,而不是NULL或别的索引。











