WHERE子句对索引列做加减乘除或函数操作会导致索引失效,因B+树依赖原始值有序性;应改写为等价无列运算形式、调整索引列序、确保类型一致,并用EXPLAIN验证。

WHERE子句对索引列做加减乘除,索引直接失效
MySQL的B+树索引依赖字段原始值的有序性。一旦在WHERE中对索引列执行数学运算(如price + 10 > 100、id * 2 = 200),优化器就无法将查询条件映射到索引树的搜索路径上——因为索引里存的是id本身,不是id * 2。结果只能全表扫描。
- ❌ 失效写法:
SELECT * FROM orders WHERE amount - 50 > 200 - ✅ 改写为等价不运算形式:
SELECT * FROM orders WHERE amount > 250 - ✅ 或提前计算常量:
SELECT * FROM orders WHERE amount > (200 + 50)(注意括号不改变语义,但避免在列上运算) - ⚠️ 特别注意:即使是看似无害的
id = 100 + 1也属于“对列运算”,应写成id = 101
日期/时间字段用YEAR()、MONTH()等函数,等于主动扔掉索引
像YEAR(create_time) = 2024这种写法,本质是把每个create_time值喂给YEAR()函数再比对,索引的B+树结构完全无法参与。哪怕该字段上有KEY(create_time),也白搭。
- ❌ 失效写法:
SELECT * FROM logs WHERE DATE(log_time) = '2024-04-10' - ✅ 改用范围查询(推荐):
SELECT * FROM logs WHERE log_time >= '2024-04-10' AND log_time - ✅ 或预计算边界:
SET @start = '2024-04-10'; SET @end = '2024-04-11'; SELECT * FROM logs WHERE log_time >= @start AND log_time - ? 范围写法还能命中
range类型的执行计划,比ALL快几个数量级
复合索引中范围查询后,右侧列索引自动“断连”
比如有联合索引INDEX idx_age_dept_salary (age, dept_id, salary),当写WHERE age = 30 AND dept_id > 5 AND salary = 8000时,salary列的索引实际没用上——因为dept_id > 5是范围条件,B+树无法继续按salary排序向下精确定位。
- ✅ 把等值列前置:
WHERE age = 30 AND salary = 8000 AND dept_id > 5(仍只用到age和salary) - ✅ 或重构索引顺序:
INDEX idx_age_salary_dept (age, salary, dept_id),让范围列放在最后 - ⚠️ 不要指望优化器“聪明”到重排条件顺序——它严格按索引定义的列序匹配
隐式类型转换和字符串引号缺失,是线上最隐蔽的索引杀手
比如user_id是VARCHAR,但代码拼SQL时传了数字12345没加引号,MySQL会自动转成CAST(user_id AS SIGNED) = 12345,这和显式调用函数效果一样:破坏有序性,索引失效。
- ❌ 失效场景:
SELECT * FROM users WHERE user_id = 12345(user_id为VARCHAR) - ✅ 强制类型一致:
SELECT * FROM users WHERE user_id = '12345' - ✅ MyBatis中务必用
#{}而非${},避免字符串拼接丢引号 - ? 验证方法:用
EXPLAIN看key是否为NULL,type是否退化为ALL
真正难的不是记住这些规则,而是在线上慢查询日志里一眼识别出哪条WHERE子句正在偷偷绕过索引——尤其当它裹着ORM生成的SQL、带参数绑定、还混着函数调用时。


















