JSON字段直接WHERE查询慢是因为MySQL不支持JSON索引,需全表解析;正确做法是用VIRTUAL虚拟列提取字段(如data->>'$.status')并建索引,使查询退化为B+树查找。

JSON字段直接WHERE查询为什么慢得像卡住
因为MySQL对JSON类型字段默认不支持索引,每次查都要全表解析JSON文本,哪怕只取$.status这种简单路径,也会触发逐行JSON_EXTRACT()计算。尤其当表有几十万行、JSON内容稍复杂时,WHERE JSON_EXTRACT(data, '$.status') = 'done'基本等于放弃性能。
用虚拟列+索引才是正解
把JSON里要查的字段“抽出来”,定义成GENERATED ALWAYS AS虚拟列,再给它加普通索引——这样查询就退化成标准B+树查找,速度能从秒级降到毫秒级。
- 虚拟列必须声明
STORED或VIRTUAL;实际只查不用存,选VIRTUAL更省空间 - 表达式必须确定、无副作用,例如
data->>'$.user_id'合法,UUID()或NOW()不行 - 类型要显式指定,比如
user_id INT或status VARCHAR(20),否则索引可能失效 - 建完虚拟列后,务必手动执行
CREATE INDEX idx_status ON orders (status);
示例:
ALTER TABLE orders ADD COLUMN status VARCHAR(20) GENERATED ALWAYS AS (data->>'$.status') VIRTUAL;
JSON路径语法写错会导致虚拟列值为NULL
->和->>区别很关键:前者返回带引号的JSON字符串(如"active"),后者自动去引号转成标量(active)。如果字段是字符串类型,又用了->,虚拟列存的是"active",但你WHERE里写= 'active'就永远不匹配。
- 查字符串/数字字段,一律用
->>(等价于JSON_UNQUOTE(JSON_EXTRACT())) - 查嵌套对象或数组,才考虑
->,但这类值无法直接建索引 - 路径不存在时,
->>返回NULL,所以要确认JSON结构稳定,或加IS NOT NULL过滤
虚拟列索引在UPDATE/INSERT时有轻微开销
每次插入或更新含JSON的行,MySQL要重新计算虚拟列表达式。实测单行开销增加不到0.1ms,但高频写入+复杂JSON路径(比如多层$.a.b.c.d)可能累积延迟。如果写远大于读,或者JSON结构频繁变更,就得权衡:要么精简路径,要么改用应用层预拆解+普通字段。
另外注意低版本兼容性:GENERATED列要求MySQL 5.7.8+,5.6及更低版本不支持,别在旧环境硬上。
最常被忽略的是:虚拟列建好后,老SQL没改写,还在用JSON_EXTRACT(),索引完全白搭。真正生效的前提,是WHERE条件里直接引用虚拟列名,而不是再去解析原JSON字段。










