MySQL 5.7中WHERE含函数条件(如DATE()、JSON_EXTRACT)会导致全表扫描,应改用虚拟列+索引优化;需确保表达式确定、仅引用本表字段,且查询时类型一致、避免混用原始字段,否则索引失效。

WHERE里多个函数条件拖慢查询?先看能不能转成虚拟列
在MySQL 5.7的环境里,函数索引是个缺失的功能。这意味着,如果你在WHERE子句里直接使用DATE(created_at) = '2024-01-01'或者JSON_EXTRACT(data, '$.status') = 'active'这类写法,数据库引擎别无选择,只能进行全表扫描来逐行计算。那么,出路在哪里?虚拟列就是为此而生的:它允许你将计算逻辑“固化”到列的定义中,然后再为这个虚拟列建立索引,从而绕过函数无法索引的限制。
不过,这里有几个关键限制必须牢记。虚拟列的定义必须是一个IMMUTABLE(确定性)的表达式,像JSON_UNQUOTE(JSON_EXTRACT(extra, _utf8mb4'$.age'))这样的组合是可以的。但反过来,NOW()、CURRENT_USER()这类每次调用结果都可能不同的非确定性函数,则绝对不允许使用——否则在创建表时就会直接报错。
- 虚拟列必须是确定性的表达式,MySQL在启动时就会进行校验。
- 表达式里不能引用其他表的字段,只能使用本表已有的列。
- 如果原始字段是
JSON类型,通常推荐使用JSON_EXTRACT提取后再用JSON_UNQUOTE去掉引号,将其转换为INT或VARCHAR等标量类型来定义虚拟列。
虚拟列加索引后为什么还是没走?检查 type 和 key 字段
当你执行了类似ALTER TABLE orders ADD COLUMN status_code TINYINT GENERATED ALWAYS AS (CASE WHEN extra->>'$.type' = "vip" THEN 1 ELSE 0 END) VIRTUAL;的操作,并为其创建了索引CREATE INDEX idx_status_code ON orders(status_code);之后,先别急着用业务SQL去测试。
更稳妥的做法是,先跑一下EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE status_code = 1;。重点观察输出结果中的"key": "idx_status_code"和"type": "ref"(或"range")。如果这里显示的依然是"type": "ALL",那就意味着索引依然没有生效。常见的原因不外乎以下几种:
- 查询语句中混用了原始的JSON字段。例如,同时写了
WHERE status_code = 1 AND extra->>'$.type' = "vip",优化器可能会因为需要同时处理两种形式的条件而放弃使用虚拟列索引。 - 虚拟列的数据类型与
WHERE条件中字面量的类型不一致。比如虚拟列是TINYINT,但查询却写成了WHERE status_code = '1'(字符串),这会触发隐式类型转换,导致索引失效。 - 表的数据量太小(比如只有几十条记录),优化器判断全表扫描的成本可能比走索引回表还要低,因此选择了前者。
virtual 和 stored 类型怎么选?写多读少就别碰 stored
VIRTUAL是默认且通常被推荐的类型:它不占用实际的磁盘存储空间,只在查询时按需计算;其索引中存储的是物化后的值,因此查询速度快,且对数据写入操作没有额外的负担。
而STORED类型则会将计算结果实实在在地写入聚簇索引,相当于在表中多存储了一列数据。它的好处是支持被用作主键、外键或唯一约束。但坏处也很明显:
- 每次执行
INSERT或UPDATE时,都需要多计算一次、多写入一次,这会显著增加写操作的负载。 - 聚簇索引因此变大,可能导致缓冲池(Buffer Pool)的命中率下降,间接拖慢所有相关的查询。
- 一旦创建,无法从
VIRTUAL类型改为STORED类型,必须删除列后重建。
所以,除非你明确需要将这个列设置为主键或添加UNIQUE约束,否则,一律使用VIRTUAL类型是更明智的选择。
联合索引能解决的场景,别硬上虚拟列
必须清醒地认识到,虚拟列并非万能解药。举个例子,如果查询条件是WHERE status = 1 AND category = 'book' AND created_at > '2025-01-01',这三个字段都是普通的等值或范围查询,那么优先考虑建立一个联合索引INDEX idx_status_cat_created (status, category, created_at),效果会更好。
在这种场景下强行使用虚拟列,反而是画蛇添足:它增加了不必要的计算层、带来了额外的索引维护成本,甚至还可能干扰优化器对更优联合索引的选择。虚拟列的真正用武之地,是当查询条件中间出现了函数、JSON提取、类型转换等这些“天生无法被直接索引的操作”时。
还有一个容易被忽略的细节:虚拟列索引和原始字段的索引,不能被包含在同一个联合索引中。像INDEX (status, virtual_col)这样的语法是非法的,MySQL会直接报错。这一点在规划索引策略时需要特别注意。
