where子句中对索引列使用函数(如year()、upper())会导致索引失效,因b+树只存储原始值;应改写为范围查询或使用mysql 8.0+函数索引,并避免隐式类型转换。

WHERE 子句中对索引列使用函数(如 YEAR()、UPPER()、DATE())时,MySQL 无法走索引——不是因为索引建错了,而是因为索引根本“认不出”计算后的值。
B+ 树索引只存原始列值,不存函数结果。优化器要查 YEAR(create_time) = 2023,就得把每一行的 create_time 拿出来算一遍 YEAR(),再比对,等于主动放弃索引的有序性优势,只能全表扫描。
YEAR()、UPPER() 这类函数为什么让索引失效
- 索引结构基于原始值构建,
YEAR('2023-05-12')是计算过程,不是存储项 - MySQL 不会(也不能)为每个函数组合预建索引,除非你显式定义函数索引(MySQL 8.0+)
- 即使该列有
INDEX idx_ct (create_time),WHERE YEAR(create_time) = 2023依然触发type: ALL
常见等价写法错误包括:
WHERE DATE(created_at) = '2023-01-01'WHERE UPPER(name) = 'ALICE'WHERE id + 1 = 1001WHERE CONCAT(first_name, ' ', last_name) = 'John Doe'
怎么改写才能让索引生效
核心原则:把函数从列上挪走,挪到常量侧,转成范围或等值条件
-
YEAR(create_time) = 2023→create_time >= '2023-01-01' AND create_time -
DATE(created_at) = '2023-01-01'→created_at >= '2023-01-01 00:00:00' AND created_at -
UPPER(name) = 'ALICE'→ 改用大小写不敏感的校对集(如utf8mb4_0900_as_cs),或确保name字段本身是COLLATE utf8mb4_0900_as_cs,直接查name = 'alice' - 需要前缀大写匹配?考虑生成列:
ALTER TABLE users ADD name_upper VARCHAR(64) STORED AS (UPPER(name)),再在name_upper上建索引
MySQL 8.0+ 的函数索引不是万能解药
- 函数索引只对显式声明的表达式有效,比如
CREATE INDEX idx_year ON orders ((YEAR(create_time))) - 它不会自动覆盖所有函数用法;写查询时仍需严格匹配索引定义的表达式
-
WHERE YEAR(create_time) = 2023能命中上面的函数索引,但WHERE create_time > NOW() - INTERVAL 1 YEAR依然不能 - 函数索引字段不可
UPDATE直接赋值,且占用额外存储空间
真正容易被忽略的是隐式转换:哪怕没写函数,WHERE phone = 13800138000(phone 是 VARCHAR)也会触发类型转换,导致索引失效。这种问题不会报错,EXPLAIN 里却悄悄变成 key: NULL。











