必须用stored而非virtual,因为mysql 5.7中virtual列建索引时索引节点存的是计算指令,每次查询仍需实时解析json;而stored列在写入时即固化提取值并落盘,二级索引才能基于具体值进行b+树查找,真正实现where user_id = 123走type: ref。

MySQL 5.7 中对 JSON 字段直接用 JSON_EXTRACT 或 -> 操作符查询,必然触发全表扫描;唯一可靠解法是用 STORED 虚拟列 + 索引,VIRTUAL 在 JSON 场景下基本无效。
为什么必须用 STORED 而不是 VIRTUAL?
MySQL 5.7 的 InnoDB 对 VIRTUAL 列建索引时,索引节点里存的仍是“计算指令”,每次查都要实时解析 JSON 字符串——和原始写法没本质区别。而 STORED 会在 INSERT/UPDATE 时把提取结果(如 data->>'$.user_id')真实写入聚簇索引,二级索引才能真正基于具体值做 B+ 树查找。
-
VIRTUAL适用于简单字符串处理(如REVERSE(name)),但 JSON 解析开销大,实时计算扛不住高并发读 -
STORED列值物理存在,WHERE user_id = 123才能稳定走type: ref - 官方文档明确:对 JSON 字段索引,必须用
STORED;VIRTUAL+ JSON 无法支持索引加速
怎么写正确的 STORED 虚拟列定义?
关键在表达式确定性、类型显式、路径安全。错一个字符,建表就报错或索引失效。
- 必须用
->>(而非->)提取字符串并自动JSON_UNQUOTE,例如:data->>'$.status' - 数值类字段要显式转类型,避免隐式转换导致索引失效:
CAST(data->>'$.age' AS UNSIGNED) - 禁止非确定函数:
NOW()、RAND()、CURRENT_USER()直接让 DDL 失败 - 嵌套数组路径如
data->>'$.items[0].name'仅在 MySQL 5.7.13+ 支持,生产前先查SELECT VERSION();
建完索引后查询仍不走?先盯这三点
EXPLAIN FORMAT=JSON 显示 "type": "ALL",大概率不是建得不对,而是用得不对。
- WHERE 条件里混用原始 JSON 字段:比如同时写了
WHERE v_user_id = 123 AND data->>'$.user_id' = '123',优化器会放弃虚拟列索引 - 类型不匹配:虚拟列是
INT,但 WHERE 写成WHERE v_user_id = '123'(字符串),触发隐式转换,索引失效 - 没真正引用虚拟列:还在用旧 SQL
WHERE data->>'$.user_id' = '123',当然不走新索引——业务代码必须改
从库加索引能缓解主从压力吗?
可以,但前提是主库已定义 STORED 虚拟列,且从库对该列单独建索引。主库不感知、不同步这个索引语句,完全可行。
- 主库执行:
ALTER TABLE logs ADD COLUMN api_code SMALLINT GENERATED ALWAYS AS (extra->>'$.code') STORED; - 从库执行:
CREATE INDEX idx_api_code ON logs(api_code); - 验证是否生效:
EXPLAIN SELECT * FROM logs WHERE api_code = 404;应看到"key": "idx_api_code" - 注意:不能只在从库加虚拟列定义,DDL 不一致会导致主从复制中断
最常被忽略的一点:STORED 虚拟列会让每次写入多一次 JSON 解析 + 一次磁盘写入,如果业务写频次高、JSON 结构深,主库 CPU 和 IOPS 会上升——得权衡读加速和写成本。











