索引失效主因是where中对字段使用函数或运算,导致无法利用b+树有序性;应改写为字段裸值在左、计算后常量在右,如year(create_time)=2023→create_time>='2023-01-01' and create_time

因为这会让索引直接失效,查询从毫秒级退化成秒级甚至分钟级——不是配置问题,也不是SQL写得不够漂亮,而是B+树索引的物理结构根本不支持。
WHERE里一用YEAR()、UPPER(),索引就“看不见”
索引文件里存的是字段原始值(比如'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' -
WHERE price * 1.1 > 100→ 改成WHERE price > 100 / 1.1,别在列上做算术运算
函数索引不是万能解,但8.0+可用作补救
MySQL 8.0.13+支持函数索引,比如CREATE INDEX idx_upper_name ON users ((UPPER(name))),但它有硬约束:
- 函数必须是确定性的(
NOW()不行,UPPER()可以) - 生成列不能是
BLOB或TEXT类型(SUBSTRING()返回类型需显式转为VARCHAR) - 查询必须完全匹配该表达式才能命中:
WHERE UPPER(name) = 'JOHN'可走索引;WHERE name LIKE 'j%'不行 - 不适用于
ORDER BY或GROUP BY中的表达式重用,仍可能触发Using filesort
比函数更隐蔽的索引失效场景
有些写法看着没函数,实际也等效于隐式函数调用:
-
WHERE create_time + INTERVAL 1 DAY > NOW()→ 字段被运算,索引失效;应改为WHERE create_time > NOW() - INTERVAL 1 DAY -
WHERE status IN ('1', '2', '3')且status是TINYINT→ 字符串字面量触发隐式类型转换,可能使索引失效;统一用数字:WHERE status IN (1, 2, 3) -
WHERE user_id = '123'(user_id是INT)→ 触发CAST(),效果和显式函数一样
真正难的不是改写SQL,而是识别那些“业务上绕不开函数”的查询——比如模糊查中间段、多字段拼接搜索。这种时候得回到数据建模本身,而不是只盯着WHERE怎么写。











