索引字段被函数包裹时索引失效,因B+树存储原始值;应将函数移至常量侧,如用范围查询替代DATE(),避免隐式转换与OFFSET分页。

WHERE条件里对索引字段用了函数,索引直接失效
只要索引列被任何函数包裹(DATE()、UPPER()、CONCAT()、甚至 id + 1),MySQL 就无法走索引——因为 B+ 树里存的是原始值,不是计算后的结果,优化器只能全表扫描逐行算。
- 常见错误写法:
WHERE DATE(create_time) = '2025-12-27'、WHERE UPPER(name) = 'ZhangSan'、WHERE id * 2 = 100 - 正确思路:把函数从索引列“挪到右边常量侧”,让索引列保持裸露状态
- 例如日期查询,别用
DATE(),改用范围:WHERE create_time >= '2025-12-27 00:00:00' AND create_time - 字符串大小写匹配,别用
UPPER(),改用校对规则:WHERE name COLLATE utf8mb4_unicode_ci = 'zhangsan'(或建索引时指定COLLATE)
联合索引中跳过最左前缀,后面字段全废
联合索引 idx_a_b_c(a,b,c) 不是“任意字段都能用”,它只支持 a、a,b、a,b,c 这三种前置组合。一旦 WHERE 条件没出现 a,哪怕写了 b = ? AND c = ?,索引也完全不生效。
- 典型踩坑:
WHERE b = 10 AND c = 20——type是ALL,不是range或ref - 如果业务真要查
b或c,要么单独建索引,要么调整联合索引顺序(比如改成idx_b_c_a(b,c,a)) - 注意:
WHERE a = 1 AND c = 20能用上a,但c不会走索引(中间断层),属于“部分命中”
字符串字段传数字参数,触发隐式类型转换
这是线上最隐蔽的事故源:字段是 VARCHAR,SQL 却传了没加引号的数字,MySQL 自动把整列转成数字比对,索引字段被函数化,必然全表扫描。
- 错误示例:
WHERE phone = 13800138000(phone是VARCHAR(11)) - 正确写法必须加引号:
WHERE phone = '13800138000' - 后端代码也要同步:Java 里别用
Long phone接参,改用String phone;MyBatis 的@Param类型必须和 DB 字段一致 - 检查慢日志时,重点看
EXPLAIN的type是否为ALL,再对比 SQL 中值的字面量有没有引号
ORDER BY 和 LIMIT 混合使用时,索引可能被忽略
即使 WHERE 条件走了索引,ORDER BY 字段不在索引覆盖范围内,或排序方向与索引顺序冲突,MySQL 仍可能放弃索引,改用文件排序(Using filesort),尤其在 LIMIT 偏移很大时(如 LIMIT 1000000, 20)成本爆炸。
- 验证方式:
EXPLAIN结果里看Extra列是否含Using filesort - 优化方向:让
ORDER BY字段进入索引,且顺序一致(如索引是(a, b ASC),就别写ORDER BY a, b DESC) - 大偏移分页必须重构:用游标分页(
WHERE id > last_id ORDER BY id LIMIT 20),而不是OFFSET - 必要时加覆盖索引,避免回表加重排序负担
真正难的不是知道这些规则,而是在线上流量高峰时,从一堆看似正常的 SQL 里快速定位哪一行触发了隐式转换、哪个函数悄悄干掉了索引——往往就差一个引号、一个括号位置、或者一个类型声明。


















