MySQL对索引列使用函数会导致索引失效,因B+树存储原始值而非函数结果;应改写为索引列裸值在左、计算后常量范围在右,如YEAR(create_time)=2023→create_time>='2023-01-01' AND create_time<'2024-01-01'。

MySQL在WHERE子句中对索引列使用函数,基本等于主动告诉优化器:“别走索引了,全表扫吧”。这不是配置问题,也不是版本缺陷,而是B+树索引的物理结构决定的硬限制。
WHERE里一用UPPER()、YEAR(),索引就“看不见”
索引文件里存的是字段原始值(比如'2023-05-12 14:30:00'或'alice'),不是函数结果。一旦写成WHERE UPPER(name) = 'ALICE'或WHERE YEAR(create_time) = 2023,MySQL必须逐行取值、调函数、比结果——没法利用B+树的有序性跳转。
- 执行计划里
type=ALL或key=NULL就是明确信号 - 哪怕该字段单独建了索引,或在联合索引最左列,也照样失效
-
DATE()、SUBSTRING()、CONCAT()、LENGTH()等单行函数同理
怎么改写才能让索引重新生效
核心原则:让索引列以“裸值”形式独立出现在WHERE左侧,右侧用计算后的常量范围去匹配。
-
WHERE YEAR(create_time) = 2023→ 改成WHERE create_time >= '2023-01-01' AND create_time -
WHERE UPPER(name) = 'JOHN'→ 改成WHERE name IN ('john', 'John', 'JOHN')(若大小写变体有限)或统一存储小写后查name = 'john' - 真要按年/月/日高频查询,考虑生成列:
ADD COLUMN create_year INT AS (YEAR(create_time)) STORED,再给create_year加索引
隐式转换和类型不一致,本质也是“隐形函数”
看着没写函数,但MySQL在背后悄悄加了CAST(),效果一样。
-
user_id是INT,却传字符串:WHERE user_id = '123'→ 触发隐式转换,索引失效 -
phone是VARCHAR,却传数字:WHERE phone = 13800000000→ 同样失效 - 验证方式很简单:
SHOW CREATE TABLE table_name确认字段类型,参数传入时严格保持一致
别依赖EXPLAIN之外的猜测
有没有走索引,不看SQL多漂亮,只看EXPLAIN输出的type和key字段。哪怕加了索引、语句看似合理,只要type是ALL或index、key为NULL,就是没用上。
- 特别注意
Extra里出现Using filesort或Using temporary——不一定代表索引失效,但常意味着排序/分组没命中索引,需要连带检查ORDER BY和GROUP BY字段是否在索引覆盖范围内 - 联合索引中范围查询(如
BETWEEN、>)之后的列,无论写不写进WHERE,都自动失效,这点容易被忽略


















