mysql 8.0 json字段查询必须建函数索引,且where条件须与索引表达式字面完全一致,否则全表扫描;错误写法data->>'$.field'不走索引,正确做法是create index idx on t ((cast(data->>'$.field' as type)))并严格匹配类型、路径和括号。

直接在 WHERE 里写 data->>'$.field' 永远不走索引,查得越准,性能越崩——这是 MySQL 8.0 JSON 查询最隐蔽也最致命的坑。
为什么 data->>'$.status' 查询慢到无法接受
MySQL 的 B+ 树索引只认物理列名,不认运行时表达式。data->>'$.status' 每次执行都要:解析整段 JSON 字符串 → 提取 $.status 路径 → 去掉双引号 → 比较值。优化器看不到“结构”,只能全表扫描。EXPLAIN 显示 type: ALL 就是铁证。10 万行数据下,CPU 和内存开销可能飙升百 GB 级。
常见错误现象:
- 本地测试几百条数据飞快,上线后接口超时、慢查询日志刷屏
- 明明给
data字段加了普通索引,EXPLAIN却显示key: NULL - 用
JSON_EXTRACT(data, '$.price') > 1000查价格,响应时间从 20ms 涨到 2s+
必须建函数索引,且语法不能错一个括号
MySQL 8.0+ 支持函数索引,但限制极严:外层必须是双括号 ((...)),类型要显式转换,路径必须固定。
实操建议:
- 数字字段:用
CAST(data->>'$.user_id' AS UNSIGNED),别用INT(MySQL 不识别) - 字符串字段:用
CAST(data->>'$.email' AS CHAR(100)),长度按业务最大值设,别留余量 - 数组元素提取:支持
data->>'$.tags[0]',但生产环境建议先验证版本是否 ≥ 8.0.13 - 创建语句必须带双括号:✅
CREATE INDEX idx_email ON t ((CAST(data->>'$.email' AS CHAR(100))));;❌CREATE INDEX idx_email ON t (CAST(data->>'$.email' AS CHAR(100))(少一层括号直接报错ERROR 3105)
查数组元素必须用 JSON_CONTAINS + 预处理
whereJsonContains() 这类 ORM 封装极易翻车:它默认把 PHP 变量原样拼进 SQL,若 $tag = 'php',生成的是 JSON_CONTAINS(tags, php)——非法 JSON,MySQL 直接报错。
正确做法分三步:
- 用户输入先转合法 JSON 字符串:
$tag = json_encode($userInput, JSON_UNESCAPED_UNICODE);,得到"\"php\"" - 指定路径防误匹配:
whereRaw('JSON_CONTAINS(tags, ?, "$.skills")', [$tag]),强制只查skills子数组 - 提前过滤空/非法值:
whereNotNull('tags'),否则JSON_CONTAINS对NULL或非 JSON 字符串返回0,结果为空但无报错
索引建了,查询仍不走?检查这三点
函数索引建完 ≠ 自动生效。90% 的“已建索引但无效”问题出在这三个地方:
- 查询条件和索引表达式必须字面完全一致:✅
WHERE CAST(data->>'$.status' AS CHAR(20)) = 'active';❌WHERE data->>'$.status' = 'active'(哪怕逻辑等价,也不走索引) - 隐式类型转换废索引:虚拟列是
UNSIGNED,但传入字符串'123';或末尾多空格'shipped ' - 函数包装必废索引:
WHERE UPPER(status) = 'SHIPPED'—— 虚拟列索引彻底失效,连LOWER()都不行
真正难的不是建索引,而是让每一条查询语句都精确对齐索引定义的表达式——路径、类型、括号、空格,差一点,就回到全表扫描。











