json字段直接where查询慢是因为mysql不支持json索引,需全表扫描并解析每个json文本;应改用虚拟列+索引:add column status varchar(20) generated always as (json_column->>"$.status") stored, add index idx_status (status)。

JSON字段直接WHERE查询为什么慢得像卡住
因为MySQL对JSON类型字段默认不支持索引,每次WHERE json_column->'$.status'都要全表扫描+解析整个JSON文本。哪怕只查一个status字段,也要把每个JSON对象反序列化一遍——数据量一过十万行,响应就明显拖沓。
- 别用
JSON_CONTAINS()或JSON_EXTRACT()在WHERE里直接查,除非数据量<1000行 -
->和->>操作符本质是运行时函数调用,无法走索引 - 如果必须查JSON内字段,优先考虑拆成普通列;实在不行,再用虚拟列+索引
怎么给JSON里的status建索引:虚拟列+普通索引两步走
核心思路是把JSON路径提取结果“固化”为一个可索引的列,MySQL 5.7+ 支持GENERATED ALWAYS AS虚拟列,不占额外存储空间,但能建索引。
ALTER TABLE orders ADD COLUMN status VARCHAR(20) GENERATED ALWAYS AS (json_column->>'$.status') STORED, ADD INDEX idx_status (status);
-
STORED比VIRTUAL更稳妥:某些旧版本MySQL对VIRTUAL列的索引支持有bug - 必须用
->>(带去引号),否则存的是带双引号的字符串,比如"shipped",导致WHERE status = 'shipped'匹配失败 - 虚拟列的数据类型要和实际值匹配:
INT就别用VARCHAR,否则隐式转换让索引失效
虚拟列索引失效的三个典型场景
建了索引不代表一定生效,这几个地方最容易踩空:
- 查询条件写成
WHERE status = 'shipped '(末尾多空格)——虚拟列值没trim,但你没意识到 - JSON路径不存在时,
json_column->>'$.status'返回NULL,而WHERE status IS NULL能走索引,但WHERE status = ''不能 - 用了函数包装查询,比如
WHERE UPPER(status) = 'SHIPPED',虚拟列索引完全失效
什么时候不该用虚拟列索引
不是所有JSON字段都适合这条路。如果满足以下任一条件,建议重构表结构或换方案:
- 要查的字段嵌套太深,比如
$.items[0].product.id——MySQL不支持对数组下标做稳定生成列 - JSON结构频繁变动,今天是
status,明天加了status_v2,虚拟列维护成本高 - 单条JSON特别大(>1MB),即使只取一个字段,MySQL解析开销仍不可忽略,此时ES或MongoDB更合适
虚拟列索引不是银弹,它只是把JSON字段“骗”进B+树的权宜之计。真正容易被忽略的是:一旦JSON字段更新,虚拟列值自动重算,但如果你在应用层缓存了旧值,或者依赖触发器同步其他表,这里就埋了数据不一致的雷。











