mysql 5.7 用虚拟列+索引解决函数索引缺失问题,需确保表达式确定性、类型一致且查询显式引用虚拟列,virtual 适用于读多写少过滤场景,stored 支持排序分组但增写负载。

MySQL 5.7 不支持函数索引,WHERE DATE(created_at) = '2024-01-01' 或 WHERE JSON_EXTRACT(data, '$.status') = 'active' 这类写法必然触发全表扫描;唯一可靠解法是用虚拟列把计算逻辑“固化”到列定义里,再建索引。
虚拟列必须满足确定性表达式,否则建表失败
MySQL 启动时会校验虚拟列表达式是否 immutable(不可变),非确定性函数直接报错。
-
NOW()、CURRENT_USER()、CONNECTION_ID()等绝对不能用 - 允许的典型表达式:
JSON_EXTRACT(extra, _utf8mb4'$.age')、JSON_UNQUOTE(JSON_EXTRACT(extra, _utf8mb4'$.status'))、DATE_FORMAT(create_time, '%Y-%m') - 表达式只能引用本表已有字段,不能跨表或引用其他生成列
- 若原始字段是
JSON类型,建议用->>'$.key'提取字符串,再显式转类型(如VARCHAR(32))
VIRTUAL vs STORED:选错类型会导致索引失效或写入抖动
VIRTUAL 是默认且大多数场景的首选;STORED 只在特定需求下才值得考虑。
-
VIRTUAL:不占磁盘空间,查询时实时计算,索引值物化存储——适合读多写少、无排序/分组需求的过滤场景 -
STORED:写入时计算并落盘,二级索引可完整引用,支持ORDER BY/GROUP BY——但每次INSERT/UPDATE都多一次计算+写入,写负载上升明显 - 不能从
VIRTUAL改成STORED,必须DROP COLUMN后重建 - 对从库做查询加速时,
STORED + 从库索引才真正有效;VIRTUAL列即使加了索引,从库仍需实时计算
建完虚拟列和索引后,EXPLAIN 显示 type: ALL?先查这三件事
索引没走,不是建得不对,就是用得不对。重点看 EXPLAIN FORMAT=JSON 输出里的 "key" 和 "type" 字段。
- 查询中混用原始字段:比如同时写了
WHERE v_status = 1 AND extra->>'$.status' = 'active',优化器可能放弃虚拟列索引 - 类型不一致:虚拟列是
TINYINT,但写成WHERE v_status = '1'(字符串),触发隐式转换,索引失效 - 没用虚拟列本身:仍沿用旧写法
WHERE JSON_EXTRACT(extra, '$.status') = 'active',当然不走新索引
LIKE '%abc' 这种后缀模糊查,也能用虚拟列加速
传统方案要冗余一列存反转字符串,成本高;虚拟列让这事变得轻量。
- 添加虚拟列:
ALTER TABLE users ADD COLUMN v_name VARCHAR(100) GENERATED ALWAYS AS (REVERSE(name)) VIRTUAL; - 建索引:
CREATE INDEX idx_v_name ON users(v_name); - 改写查询:
WHERE v_name LIKE REVERSE('abc') + '%'→ 实际等价于原name LIKE '%abc' - 注意:如果既要
LIKE 'abc%'又要LIKE '%abc',可用UNION合并两个走索引的查询
虚拟列不是银弹——联合索引能覆盖的场景(比如 WHERE a = ? AND b = ?),别硬上虚拟列;它真正发力的地方,是那些绕不开函数计算、又高频过滤的字段,比如 JSON 提取值、日期截断、字符串变形。最容易被忽略的是类型一致性检查和查询语句是否真正切换到了虚拟列——这两步漏掉,前面所有 DDL 都白做了。











