WHERE中对索引列使用函数(如YEAR、UPPER)会导致索引失效,因B+树存储原始值而非计算结果;应改写为范围查询,使索引列以裸值出现在操作符左侧。

WHERE里用YEAR(create_time)这类函数,索引就废了
因为索引B+树里存的是create_time的原始值(比如'2026-09-21 14:30:00'),不是YEAR()算出来的2026。优化器没法拿着2026去B+树里二分查找——树里根本没有这个“年份序列”,只有完整时间戳的有序排列。
它只能退化为全表扫描:逐行取出create_time,调用YEAR()函数,再比对结果。哪怕表只有1万行,也要执行1万次函数调用+1万次判断。
常见失效写法包括:
WHERE DATE(create_time) = '2026-09-21'WHERE UPPER(name) = 'ABC'WHERE id + 1 = 100WHERE SUBSTR(phone, 1, 3) = '138'
怎么改才能让索引重新生效
核心原则:让索引列以“裸值”形式出现在=、>=等操作符左侧,把计算逻辑挪到右侧或改写为范围条件。
例如:
- 原句:
WHERE YEAR(create_time) = 2026→ 改为:WHERE create_time >= '2026-01-01' AND create_time - 原句:
WHERE DATE(create_time) = '2026-09-21'→ 改为:WHERE create_time >= '2026-09-21 00:00:00' AND create_time - 原句:
WHERE id + 1 = 100→ 改为:WHERE id = 99
注意:BETWEEN写法容易漏掉边界精度(比如BETWEEN '2026-09-21' AND '2026-09-21'只查当天0点),推荐用左闭右开区间更可控。
MySQL 8.0+ 的CREATE INDEX ... (UPPER(name))能救急吗
可以,但限制极多,不能当常规解法用。
函数索引生效的前提是:
- 查询中使用的函数表达式必须和索引定义**字符级完全一致**,空格、大小写、参数顺序都不能差;
- 只支持确定性函数(
UPPER、SUBSTR、TRIM等),不支持NOW()、RAND(); -
ORDER BY UPPER(name)不会自动利用该索引,除非语句里也明确写了ORDER BY UPPER(name); - 备份恢复、主从同步、低版本兼容性都可能出问题。
真要用,先在测试环境跑EXPLAIN确认key字段确实命中了函数索引名,别凭感觉。
EXPLAIN里看到type=ALL就一定是函数惹的祸吗
不一定。虽然函数运算是高频原因,但type=ALL只是表征“全表扫描”,背后可能还有别的根因:
- 联合索引没走最左前缀,比如索引是
(user_id, status, create_time),但WHERE status = 1跳过了user_id; - 隐式类型转换,比如
WHERE user_id = '123'(user_id是INT),MySQL会把每行user_id转字符串再比,索引失效; - OR条件里混入了无索引列,比如
WHERE indexed_col = 1 OR unindexed_col = 'x'; - 统计信息过期,
ANALYZE TABLE后rows预估可能变化,type也可能变。
函数运算的问题最容易被忽略,因为它看起来“逻辑正确”;但只要WHERE里出现任何对索引列的包裹或计算,第一反应就该是把它拎出来单独验一遍EXPLAIN。


















