json字段直接查询慢是因为features->'$.color'等表达式无法走索引,mysql必须全表扫描并实时解析每行json;应建virtual虚拟列v_color varchar(20) generated always as (features->>'$.color')并为其创建b+树索引,优化器会自动重写查询命中索引。

JSON字段直接查询为什么慢
因为 features->'$.color' 这类表达式无法走索引,MySQL 必须对每一行的 JSON 字符串做实时解析和提取,等价于全表扫描。哪怕只有 10 万行,SELECT * FROM products WHERE features->'$.color' = 'Red' 的 EXPLAIN 显示 type: ALL,耗时从毫秒级跳到秒级。
根本原因不是 JSON 解析本身慢,而是 B+ 树索引压根“看不见” JSON 内部结构——它只认普通列或生成列。
添加虚拟生成列 + 索引是最小侵入方案
不用改业务代码、不迁移数据、不增加存储开销,只要两步 DDL:
-
ALTER TABLE products ADD COLUMN v_color VARCHAR(20) GENERATED ALWAYS AS (features->>'$.color') VIRTUAL;—— 注意用->>(去引号提取),不是->,否则值带双引号,索引匹配会失败 -
CREATE INDEX idx_v_color ON products(v_color);—— 虚拟列可以建普通 B+ 树索引,且索引数据真实落盘
之后查询 WHERE features->>'$.color' = 'Red' 或 WHERE v_color = 'Red' 都能命中索引。MySQL 优化器会自动将前者重写为后者(见 SHOW WARNINGS 输出)。
哪些场景必须用 STORED 而非 VIRTUAL
虚拟列(VIRTUAL)适合大多数情况,但以下情形建议改用 STORED:
- 该字段被高频
ORDER BY或GROUP BY——VIRTUAL每次排序都重新计算,STORED直接读磁盘值 - 需要在该列上建
UNIQUE约束 —— MySQL 不允许对VIRTUAL列加唯一约束 - 字段提取逻辑复杂(如嵌套多层 +
JSON_EXTRACT+ 字符串处理)且查询量极大 —— 避免重复计算开销
代价是:每行数据多存一份计算结果,INSERT/UPDATE 时需额外计算并写入。
容易踩的坑和兼容性注意点
常见翻车点集中在类型和表达式一致性上:
-
features->>'$.color'返回的是字符串,所以生成列类型必须是VARCHAR/TEXT,不能是INT;若 JSON 里存的是数字({"price": 99}),得用CAST(features->>'$.price' AS SIGNED)配合INT类型列 - 表达式必须完全一致:优化器只匹配字面等价的表达式,
features->>'$.color'和JSON_UNQUOTE(JSON_EXTRACT(features, '$.color'))不会被识别为同一逻辑 - MySQL 5.7.8+ 才支持
->>;低于此版本只能用JSON_EXTRACT(),且要注意返回值带引号 - JSON 数组字段(如
"tags": ["red", "sale"])不能用单值虚拟列索引,得等 MySQL 8.0.17+ 的多值索引(AS (CAST(tags AS CHAR ARRAY)))
最常被忽略的是:虚拟列本身不占空间,但索引会真实占用磁盘——别以为“虚拟”就等于“零成本”。大表加索引前务必评估索引大小和重建耗时。











