JSON字段WHERE中用->>等函数无法走索引,因B+树索引无法下推运行时解析;优化需用虚拟列+索引、函数索引或多值索引。

直接用 JSON_EXTRACT() 或 ->> 在 WHERE 里过滤,基本等于放弃索引——数据一过万,查询就卡住。
为什么 details->>'$.status' = 'done' 永远不走索引
MySQL 的 B+ 树索引只能匹配原始列值,而 ->> 是运行时函数:每行都要解析整个 JSON 字符串、剥离引号、提取字段、再比较。优化器根本没法把这一步“下推”到索引查找中。
- 哪怕你对
details字段建了普通索引(CREATE INDEX idx_details ON t(details)),也完全无效 -
JSON_EXTRACT(details, '$.status')、details->'$.status'、details->>'$.status'全部触发全表扫描 - 线上千万级表上,这类查询常超 5 秒,且 CPU 占用飙升
真正能走索引的三种写法(MySQL 8.0+)
必须把 JSON 内部字段“固化”成一个可索引的列,而不是每次查询都现场解析。
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
-
方案一:虚拟列 + 普通索引(推荐)
添加一个GENERATED ALWAYS AS虚拟列,再对其建索引:ALTER TABLE logs ADD COLUMN status VARCHAR(20) GENERATED ALWAYS AS (details->>'$.status') STORED;<br>CREATE INDEX idx_status ON logs(status);
之后WHERE status = 'error'就能走索引 -
方案二:虚拟列 + 函数索引(MySQL 8.0.13+)
跳过新增列,直接在表达式上建函数索引:CREATE INDEX idx_status_func ON logs((details->>'$.status'));
注意括号不能少,这是 MySQL 函数索引的语法要求 -
方案三:JSON 列上的多值索引(仅适用数组字段)
如果要查的是 JSON 数组里的某个值(如tags是["vip", "trial"]),可用:ALTER TABLE users ADD COLUMN tags_json JSON;<br>CREATE INDEX idx_tags ON users((CAST(tags_json AS CHAR ARRAY)));<br>SELECT * FROM users WHERE 'vip' MEMBER OF (tags_json);
WHERE 里临时用 JSON 函数的底线操作
实在没法改表结构(比如只读从库、临时排查),又必须用 JSON 过滤,至少守住三条线:
- 加
JSON_VALID(details)前置判断,避免非法 JSON 导致逻辑错乱:WHERE JSON_VALID(details) AND details->>'$.type' = '1' - 字符串比较务必用
->>,别用->:details->'$.name'返回"tom",跟'tom'永远不等 - 数字/布尔字段别信直觉,显式转换:
CAST(details->>'$.score' AS UNSIGNED) > 90,而不是details->>'$.score' > 90(隐式转换可能出错)
虚拟列不是“锦上添花”,是 JSON 查询性能的分水岭。没它,所有 WHERE 里的 JSON 解析都是裸奔;有了它,才能把 JSON 字段当普通字段用。路径写错、NULL 静默返回、类型带引号——这些都只是表层问题,真正的瓶颈永远在索引能不能命中。

















