索引列在WHERE中使用DATE()或UPPER()等函数会导致索引失效;MySQL 5.7及以前版本无法利用B+树索引匹配函数结果,EXPLAIN显示type: ALL、key: NULL;应改写为范围查询,如created_at >= '2024-01-01' AND created_at < '2024-01-02'。

WHERE 条件里对索引列用 DATE() 或 UPPER() 就失效了
MySQL 5.7 及以前版本,只要在 WHERE 子句中对索引列施加函数(比如 WHERE DATE(created_at) = '2024-01-01'),哪怕该列本身有索引,优化器也基本放弃走索引——它没法用 B+ 树直接匹配函数结果。
常见错误现象:EXPLAIN 显示 type: ALL、key: NULL,哪怕表只有几万行,查询也变慢;线上慢查日志里频繁出现这类写法。
- 优先改写为范围查询:
WHERE created_at >= '2024-01-01' AND created_at - 字符串大小写比较别用
UPPER(col) = 'ABC',改用col = 'abc'并确保列用utf8mb4_0900_as_cs或类似大小写敏感 collation - 实在绕不开函数逻辑(比如按月聚合),考虑把计算结果存成新字段,再建索引
MySQL 5.7+ 虚拟列 + 索引能救急,但要注意存储引擎限制
虚拟列(GENERATED ALWAYS AS)本身不占物理空间,但给它加索引后,MySQL 会实际存储一份冗余值(InnoDB 引擎下),相当于隐式物化。这对磁盘和写入性能有真实影响。
使用场景:需要高频按某表达式查询,又不想改应用层 SQL,比如固定提取邮箱域名做统计:email_domain VARCHAR(64) GENERATED ALWAYS AS (SUBSTRING_INDEX(email, '@', -1)) STORED。
- 必须用
STORED才能建索引;VIRTUAL列不支持索引 - 仅 InnoDB 支持虚拟列索引;MyISAM 不行
- 虚拟列定义不能引用非确定性函数(如
NOW()、RAND()),否则建表失败 - ALTER TABLE 添加虚拟列索引是锁表操作(5.7),8.0 支持
ALGORITHM=INSTANT仅限某些简单情况
MySQL 8.0 函数索引不是“给任意函数建索引”,而是有限制的表达式索引
8.0 的 CREATE INDEX idx ON t ((UPPER(name))) 看似自由,其实底层只接受「确定性、无副作用、可比较」的表达式。像 JSON_EXTRACT()、嵌套函数、或含用户变量的表达式,建索引会直接报错。
错误信息典型示例:ERROR 3750 (HY000): Expression #1 of index definition is not allowed in generated columns or index expressions.
- 支持的常见模式:
(col1 + col2)、(UPPER(col))、(LEFT(col, 10))、(DATE(col)) - 不支持:
(NOW() - col)、(IFNULL(col, 'default'))(8.0.13+ 才支持部分控制流函数)、(JSON_VALUE(json_col, '$.field'))(需 8.0.21+ 且配置正确) - 函数索引字段在
EXPLAIN中显示为func,不是原列名,排查时别漏看key和key_len - WHERE 中必须**完全一致**地写出该表达式,
WHERE UPPER(name) = 'ABC'能命中,WHERE name LIKE 'a%'不能
虚拟列和函数索引选哪个?看 MySQL 版本和变更成本
如果已经是 MySQL 8.0.13+,且查询条件固定、表达式简单,优先用函数索引——不用改表结构,语法干净,维护成本低。但若要兼容 5.7,或表达式复杂(比如涉及多列拼接+正则预处理),虚拟列更可控。
容易被忽略的一点:函数索引无法用于 ORDER BY 优化,除非 ORDER BY 子句与索引表达式**字面完全一致**;而虚拟列索引跟普通列一样,能同时支撑 WHERE 和 ORDER BY。
- 线上已用 5.7?别硬升 8.0 试函数索引,先用虚拟列 +
STORED解决燃眉之急 - 8.0 环境但表达式含
CASE WHEN或自定义函数?目前仍得靠虚拟列 - 所有方案都绕不开一点:索引列上做函数操作,本质是把计算压力从查询时挪到写入时,评估好写入放大和磁盘增长


















