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 前瞄一眼。

索引失效四大场景 索引失效 隐式类型转换 函数操作 LIKE模糊查询 OR条件 常见写法 WHERE col = 123 (col 是 VARCHAR) → 隐式转数字比较 常见写法 WHERE DATE(col) = '...' WHERE YEAR(col) = 2024 → 函数破坏索引顺序 常见写法 WHERE col LIKE '%abc' WHERE col LIKE '%abc%' → 无法前缀匹配 常见写法 WHERE col1='a' OR col2='b' (col2 无索引) → 全表扫描 核心原则:保持索引字段「原样」参与比较 不套函数、不隐式转换、不破坏前缀、不混用无索引条件 💡 写 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 都格外小心。希望你不用踩这个坑。