索引列在where中使用date()或upper()等函数会导致索引失效;mysql 5.7及以前版本无法利用b+树索引匹配函数结果,explain显示type: all、key: null;应改写为范围查询,如created_at >= '2024-01-01' and created_at
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或自定义函数?目前仍得靠虚拟列- 所有方案都绕不开一点:索引列上做函数操作,本质是把计算压力从查询时挪到写入时,评估好写入放大和磁盘增长












