对索引列使用year()、date()等函数会导致索引失效,因b+树仅存储原始值;应改写为范围查询,如year(create_time)=2025→create_time>='2025-01-01' and create_time

WHERE里对索引列调用YEAR()、DATE()这类函数,索引就废了
因为B+树索引只存字段原始值(比如create_time存的是'2025-06-19 14:22:03'),不存函数结果。优化器看到YEAR(create_time) = 2025,根本没法把“2025”映射回索引里的时间戳序列——它得把每一行的create_time先算一遍YEAR(),再比对,等于放弃索引、全表扫描。
常见错误写法包括:
WHERE DATE(create_time) = '2025-06-01'WHERE UPPER(name) = 'ADMIN'WHERE SUBSTR(phone, 1, 3) = '138'WHERE id + 1 = 100
这些都不是“可能不走索引”,而是基本确定跳过索引。
EXPLAIN里key为NULL、type是ALL,就是函数惹的祸
执行EXPLAIN SELECT * FROM user WHERE YEAR(create_time) = 2025,如果返回:
type: ALL key: NULL Extra: Using where
就坐实了索引失效。注意:Extra里只出现Using where(没有Using index或Using index condition)也是强信号。
自查要点:
- 检查
WHERE子句左侧是否裸露字段名,有没有被任何函数/运算包裹 - 确认字段类型和传入值类型是否一致(比如
user_id是INT,别传'123'字符串) - 用
SHOW CREATE TABLE核对索引定义,看是否真建在目标列上
改写原则:把计算移到等号右边,让左边保持“裸字段”
核心思路是让索引列以原始形态参与比较,把函数逻辑转成范围条件或等值偏移。
错误写法 → 正确改写:
-
YEAR(create_time) = 2025→create_time >= '2025-01-01' AND create_time (左闭右开,避免微秒截断) -
DATE(create_time) = '2025-06-01'→create_time >= '2025-06-01' AND create_time -
id + 1 = 100→id = 99 -
SUBSTR(name, 1, 3) = 'adm'→name LIKE 'adm%'(前提是能接受前缀匹配语义)
别用BETWEEN拼时间范围,容易漏掉23:59:59.999这种微秒数据。
MySQL 8.0+ 的函数索引不是自动生效的解药
虽然可以建CREATE INDEX idx_upper_name ON users ((UPPER(name))),但它有硬约束:
- 查询中
WHERE表达式必须和索引定义**逐字一致**,空格、括号、大小写都不能差 - 只支持确定性函数(
NOW()、RAND()这类不支持) -
ORDER BY UPPER(name)不会自动利用该索引,除非显式写出ORDER BY UPPER(name) - 每次
INSERT/UPDATE都要多算一次函数值,写入压力大的表要权衡
真正容易被忽略的是:函数索引解决的是“不得不查计算结果”的场景,而不是替代规范写法。多数时候,改写SQL比加函数索引更轻量、更可控。










