4. 索引失效场景:隐式类型转换、函数操作、LIKE模糊查询、OR条件
索引建好了,查询却没变快?
这种事我遇到过太多次了。很多同学跑来问我:「老师,我明明按规范建了索引,为什么 EXPLAIN 还是显示全表扫描?」
答案往往就藏在你写的 SQL 里。索引不是万能的,它有自己的脾气。你写 SQL 的方式不对,它就不干活。
这一节,我带你看看四个最常见的索引失效场景。都是实战中踩过的坑,你记好了。
核心结论:索引失效的本质,是数据库优化器认为「走索引的成本比全表扫描还高」,或者「索引结构无法支持当前的查询条件」。说白了,就是你写的 SQL 让索引没法正常工作了。
4.1 隐式类型转换:你以为的类型,不是数据库以为的
先看一个例子。假设我们有张用户表,user_id 字段是 varchar 类型,上面建了索引。
-- 索引失效
SELECT * FROM users WHERE user_id = 1001;
-- 索引生效
SELECT * FROM users WHERE user_id = '1001';
这两条 SQL 看起来差不多,对吧?但第一条会让索引失效。
为什么?因为 user_id 是字符串,你传了个数字进去。数据库会偷偷把字段值转成数字再比较。这一转,索引就废了。
我有个朋友,线上系统突然慢得像蜗牛。查了半天,发现是 ORM 框架自动把字符串参数转成了整数。改回来,速度立刻恢复。
我的习惯:写 SQL 时,字符串字段的查询条件永远加单引号。别偷懒,也别指望框架帮你处理。你自己写清楚,最靠谱。
| 字段类型 | 查询写法 | 索引是否生效 | 原因 |
|---|---|---|---|
| VARCHAR | WHERE col = 123 |
❌ 失效 | 隐式转为数字比较 |
| VARCHAR | WHERE col = '123' |
✅ 生效 | 类型匹配 |
| INT | WHERE col = '123' |
✅ 生效 | MySQL 会优化,但建议用数字 |
| INT | WHERE col = 123 |
✅ 生效 | 类型匹配 |
注意:隐式类型转换不仅影响索引,还可能改变查询结果。比如 WHERE '10' > 9 在字符串比较下是 false,但数字比较下是 true。这种 bug 很难查。
4.2 函数操作:给索引字段「化妆」,它就认不出你了
这个场景更常见。你给索引字段套了个函数,数据库就懵了。
-- 索引失效
SELECT * FROM orders WHERE DATE(create_time) = '2024-01-01';
-- 索引生效(范围查询)
SELECT * FROM orders WHERE create_time >= '2024-01-01 00:00:00'
AND create_time < '2024-01-02 00:00:00';
为什么会这样?因为 B+Tree 索引存储的是原始值,不是函数处理后的值。你让数据库去查 DATE(create_time),它没法直接定位,只能把每一行的 create_time 都算一遍。
我曾经接手过一个慢查询优化项目。开发同学喜欢在 WHERE 里写 YEAR()、MONTH()、LEFT() 这些函数。我一个个改成等价的区间查询,查询时间从 3 秒降到了 10 毫秒。
避坑指南:如果你实在需要函数操作,可以考虑「函数索引」(MySQL 8.0+ 支持)或者「虚拟列 + 索引」。但我的建议是:能改 SQL 就别动表结构。
4.3 LIKE 模糊查询:百分号放错位置,索引就废了
模糊查询是业务里逃不掉的需求。但 LIKE 的写法很讲究。
-- 索引生效(前缀匹配)
SELECT * FROM products WHERE name LIKE '苹果%';
-- 索引失效(后缀匹配)
SELECT * FROM products WHERE name LIKE '%苹果';
-- 索引失效(中间匹配)
SELECT * FROM products WHERE name LIKE '%苹果%';
B+Tree 索引是按字符串从左到右排序的。你给个前缀,它能快速定位。但你把百分号放前面,它就没法用索引了——因为不知道从哪开始找。
嗯,这里要注意:LIKE '%苹果' 和 LIKE '%苹果%' 都会导致全表扫描。但有一种特殊情况——如果查询的列都在覆盖索引里,MySQL 可能还是会用索引扫描,但效率远不如前缀匹配。
实战建议:
- 能用前缀匹配,就别用后缀或中间匹配
- 实在需要全文搜索,考虑 Elasticsearch 或 MySQL 的全文索引
- 如果数据量不大,全表扫描也能接受,那就别纠结索引了
4.4 OR 条件:一个条件没索引,整个查询都遭殃
OR 是个坑。很多人觉得 OR 两边都有索引就没事,其实不是。
-- 假设:col1 有索引,col2 没有索引
-- 索引失效
SELECT * FROM t WHERE col1 = 'a' OR col2 = 'b';
-- 索引生效(用 UNION 替代)
SELECT * FROM t WHERE col1 = 'a'
UNION
SELECT * FROM t WHERE col2 = 'b';
为什么 OR 会导致索引失效?因为数据库需要同时满足两个条件。如果其中一个条件没有索引,它只能全表扫描。既然都要全表扫描了,那另一个条件走索引也没意义。
我见过最夸张的一次,开发同学在 OR 后面跟了个 1=1 的占位条件。结果整个查询走了全表扫描,几百万行的表查了 20 多秒。
我的做法:
- OR 两边都建索引,或者都不建
- 用 UNION 替代 OR,让每个子查询独立走索引
- 考虑用 IN 替代 OR(IN 在某些情况下能走索引)
4.5 一张图看懂:索引失效的四种场景
下面这张图总结了四种场景的核心逻辑。你可以保存下来,写 SQL 前瞄一眼。
4.6 综合避坑清单
最后,我给你整理了一份实战清单。每次写完 SQL,对照检查一遍。
| 场景 | 错误写法 | 正确写法 | 优先级 |
|---|---|---|---|
| 隐式类型转换 | WHERE str_col = 123 |
WHERE str_col = '123' |
高 |
| 函数操作 | WHERE DATE(dt) = '2024-01-01' |
WHERE dt >= '2024-01-01' AND dt < '2024-01-02' |
高 |
| LIKE 模糊查询 | WHERE name LIKE '%abc' |
WHERE name LIKE 'abc%' 或改用全文索引 |
中 |
| OR 条件 | WHERE a = 1 OR b = 2(b 无索引) |
用 UNION 拆分,或给 b 加索引 | 高 |
最后提醒一句:索引失效不是 bug,是数据库的正常行为。它只是告诉你:「你给的查询条件,我没法用索引高效处理。」这时候别怪数据库,改 SQL 才是正解。
我曾经花了一整个下午排查一个慢查询,最后发现只是少了个单引号。从那以后,我写 SQL 都格外小心。希望你不用踩这个坑。