MySQL索引失效

qinyelin
发布于 2026-08-31 / 4 阅读
0
0

MySQL索引失效

MySQL 索引失效

面试频率:★★★★★
工作频率:★★★★★


⚡ 30 秒速记

所谓:

索引失效

并不是:

索引坏了

而是:

SQL 无法有效利用索引快速定位数据,或者优化器认为走索引成本太高,最终选择了其他执行方式,例如全表扫描。

常见情况:

1. 对索引列使用函数

2. 隐式类型转换

3. LIKE 左边使用 %

4. 联合索引不满足最左前缀

5. OR 两边索引情况不同

6. 查询返回数据太多 / 索引选择性太低

最重要的一句话:

有索引 ≠ 一定走索引,最终由 MySQL 优化器根据成本选择执行计划。


一、什么叫索引失效?

假设:

CREATE INDEX idx_name
ON user(name);

正常查询:

SELECT *
FROM user
WHERE name = '张三';

可以利用:

B+ Tree
   ↓
快速定位张三
   ↓
找到对应数据

但是某些 SQL:

无法利用B+ Tree的有序性快速定位

或者:

虽然可以利用索引

↓

但是需要查询的数据太多

↓

走索引成本反而更高

优化器就可能:

不走这个索引

↓

选择全表扫描等方式

这就是平时所说的:

索引失效

二、对索引列使用函数

假设:

CREATE INDEX idx_name
ON user(name);

正常:

SELECT *
FROM user
WHERE name = '张三';

可以:

根据name

↓

直接利用B+ Tree定位

但是:

SELECT *
FROM user
WHERE LEFT(name, 1) = '张';

变成:

name
 ↓
先执行LEFT()
 ↓
得到结果
 ↓
再判断是不是张

普通:

INDEX(name)

保存的是:

原始name值

而不是:

LEFT(name,1)

因此通常不能直接按照普通 name 索引快速定位目标范围。

可能导致:

无法有效利用普通索引定位

三、为什么函数会影响索引?

索引:

张三
张五
张小明
李四
王五

按照:

name原始值

排序。

但是查询:

WHERE LEFT(name, 1) = '张'

比较的是:

LEFT(name,1)

而不是:

name

所以普通索引原来的排序方式:

不一定能直接用于这个表达式的快速定位

四、常见函数场景

例如:

WHERE YEAR(create_time) = 2026;

如果:

create_time

有普通索引,也可能影响索引的高效利用。

更常见的优化方式是改写成范围:

WHERE create_time >= '2026-01-01'
AND create_time < '2027-01-01';

这样:

create_time

本身没有被函数包裹。

可以直接利用:

B+ Tree有序性

进行范围查询。


五、隐式类型转换

假设字段:

phone VARCHAR(20)

并且:

CREATE INDEX idx_phone
ON user(phone);

正确:

SELECT *
FROM user
WHERE phone = '13800138000';

因为:

phone

=

VARCHAR

查询参数:

'13800138000'

=

字符串

类型一致。


如果写:

SELECT *
FROM user
WHERE phone = 13800138000;

右边:

数字

左边:

VARCHAR

MySQL 可能需要:

隐式类型转换

如果转换作用到了:

索引列

就可能影响:

索引的正常快速定位

六、隐式转换怎么避免?

最简单:

Java 参数类型、数据库字段类型、SQL 参数类型尽量保持一致。

例如数据库:

phone VARCHAR

Java:

String phone;

SQL:

WHERE phone = '13800138000'

而不是:

WHERE phone = 13800138000

七、LIKE 左边有 %

这个非常经典。

假设:

CREATE INDEX idx_name
ON user(name);

查询:

WHERE name LIKE '张%';

通常可以利用索引进行范围定位。

因为:

张三
张五
张小明
张某某

具有:

确定的左侧前缀

可以找到:

以张开头的一段范围

八、LIKE '%张' 为什么不一样?

查询:

WHERE name LIKE '%张';

可能的数据:

老张

小张

张

王小张

开头是什么:

不知道

因此普通 B+ Tree:

很难根据左侧前缀快速确定查询范围

所以通常:

无法利用普通索引快速定位

同样:

WHERE name LIKE '%张%';

也没有:

确定的左侧前缀

所以通常:

无法利用普通B+ Tree索引快速定位

九、LIKE 怎么记?

LIKE '张%'

↓

左边确定

↓

通常可以利用索引范围查询

LIKE '%张'

↓

左边不确定

↓

通常无法利用普通索引快速定位

LIKE '%张%'

↓

左边不确定

↓

通常无法利用普通索引快速定位

简单记:

左边不能随便%

十、联合索引不满足最左前缀

假设:

CREATE INDEX idx_name_age_sex
ON user(name, age, sex);

索引:

name → age → sex

正常:

WHERE name = ?

可以。


WHERE name = ?
AND age = ?

也可以。


WHERE name = ?
AND age = ?
AND sex = ?

也可以。


但是:

WHERE age = ?

缺少:

最左边name

因此:

无法按照(name,age,sex)

的最左前缀进行正常快速定位

十一、不满足最左前缀 = 一定不走索引吗?

不是。

这个不要死记。

例如:

INDEX(name, age)

执行:

SELECT age
FROM user
WHERE age = 20;

虽然:

缺少最左name

但是:

age就在索引里面

优化器在某些情况下:

可能选择扫描整个索引

而不是:

扫描整张表

因为:

索引页可能比完整数据页更小

所以更准确的说法:

不满足最左前缀时,通常无法利用联合索引进行高效的索引定位,但不代表 MySQL 绝对不会扫描这个索引。


十二、OR 会导致索引失效吗?

不能简单说:

OR

=

索引失效

例如:

WHERE name = '张三'
OR age = 20;

假设:

name有索引

age没有索引

MySQL 可能判断:

name可以走索引

但是

age还是要扫描大量数据

最后:

全表扫描成本更低

于是:

直接全表扫描

十三、OR 两边都有索引呢?

例如:

name有索引

age也有索引

查询:

WHERE name = '张三'
OR age = 20;

MySQL 可能:

分别使用两个索引

然后:

合并结果

这类执行方式可能涉及:

Index Merge

所以:

出现OR

不代表:

100%索引失效

最终还是:

看执行计划

十四、!= 一定导致索引失效吗?

例如:

WHERE age != 20;

很多文章会直接说:

!=

↓

索引失效

这个说法:

不准确

真正的问题往往是:

查询结果太多

假设:

100万条数据

其中:

age = 20

只有1万条

那么:

WHERE age != 20;

意味着:

查询99万条

如果走二级索引:

找到99万个索引记录
      ↓
得到大量主键
      ↓
大量回表
      ↓
拿完整数据

成本可能非常高。

优化器可能判断:

还不如直接全表扫描

所以:

选择全表扫描

十五、NOT IN 一定失效吗?

同样:

WHERE id NOT IN (1,2,3);

不能简单说:

NOT IN一定索引失效

还是需要考虑:

返回多少数据

索引选择性

是否回表

统计信息

执行成本

最终由:

MySQL优化器

决定。


十六、索引选择性

这个概念非常重要。

假设:

sex字段

只有:

0
1

1000万用户:

男:500万

女:500万

给:

sex

建立索引:

CREATE INDEX idx_sex
ON user(sex);

执行:

SELECT *
FROM user
WHERE sex = 1;

会查询:

500万条数据

如果走二级索引:

idx_sex
   ↓
找到500万个索引记录
   ↓
得到500万个主键
   ↓
大量回表
   ↓
拿完整行数据

可能非常贵。

优化器可能觉得:

直接扫描整张表

反而更快

所以:

即使sex有索引

↓

也可能不走

十七、什么是高选择性?

例如:

身份证号
手机号
订单号
用户ID

可能:

一个值只对应很少的数据

例如:

1000万条数据

↓

phone = 138xxxx

↓

只找到1条

这种字段:

选择性高

很适合索引快速定位。


十八、什么是低选择性?

例如:

性别

是否删除

状态

可能只有:

0
1

或者:

0
1
2
3

大量数据:

值都一样

这种:

选择性低

单独建立索引:

不一定有很好的查询收益

十九、为什么有索引 MySQL 还不走? ⭐⭐⭐⭐⭐

这是今天最重要的问题。

错误理解:

字段有索引

↓

MySQL必须使用

不对。

MySQL 有:

优化器

优化器会比较:

方案A:

走索引
↓
查B+ Tree
↓
找到大量主键
↓
大量回表


VS


方案B:

直接全表扫描

然后估算:

哪个成本低?

如果:

全表扫描成本更低

那么:

即使存在索引

↓

MySQL也可能不使用

二十、索引失效真正的核心 ⭐⭐⭐⭐⭐

不要只背:

函数

LIKE %

OR

!=

类型转换

真正应该理解:

SQL
 ↓
能不能利用B+ Tree的有序性
快速定位数据?
 ↓
        ┌───────┴───────┐
        ↓               ↓
       能              不能
        ↓               ↓
   有机会走索引      可能大量扫描
        ↓
        ↓
优化器计算执行成本
        ↓
   ┌────┴────┐
   ↓         ↓
索引便宜    全表扫描便宜
   ↓         ↓
走索引     全表扫描

二十一、为什么 B+ Tree 有序性这么重要?

你前面学过:

B+ Tree叶子节点

↓

有序排列

例如:

10
20
30
40
50
60

查询:

WHERE id = 30;

可以:

快速定位30

查询:

WHERE id BETWEEN 20 AND 50;

也可以:

找到20
 ↓
沿叶子节点
 ↓
30
 ↓
40
 ↓
50

所以:

等值查询

范围查询

前缀查询

很多情况下都能利用:

B+ Tree有序性

二十二、为什么函数可能破坏这种能力?

原来:

name索引:

张三
张五
张六
李四
王五

查询:

WHERE name = '张三'

可以直接:

定位张三

但:

WHERE LEFT(name,1) = '张'

变成:

对name计算
↓
得到新的表达式结果
↓
再比较

普通:

INDEX(name)

并不是按照:

LEFT(name,1)

组织的。

所以:

原来的索引排序

可能无法直接用于快速定位

二十三、索引失效和回表的关系

有时候:

索引本身可以定位

但因为:

回表太多

优化器仍然:

放弃索引

例如:

SELECT *
FROM user
WHERE sex = 1;

索引:

idx_sex

确实可以找到:

sex = 1

但是:

500万条
 ↓
500万个主键
 ↓
大量回表

成本太高。

于是:

全表扫描

可能更划算。

所以:

索引失效

不一定是:

索引不能用

还可能是:

索引能用

↓

但是太贵

↓

优化器不想用

二十四、怎么判断到底有没有走索引?

不要猜。

使用:

EXPLAIN
SELECT *
FROM user
WHERE name = '张三';

重点看:

type

possible_keys

key

rows

Extra

例如:

possible_keys

↓

可能使用哪些索引
key

↓

最终实际选择哪个索引

如果:

key = NULL

通常表示:

没有选择索引

后面学习:

EXPLAIN

会详细讲。


二十五、常见索引失效总结

1. 索引列使用函数

例如:

WHERE YEAR(create_time) = 2026;

可能影响普通索引快速定位。


2. 隐式类型转换

例如:

phone VARCHAR

却写:

WHERE phone = 13800138000;

可能影响索引使用。


3. LIKE 左边 %

例如:

LIKE '%张'
LIKE '%张%'

通常无法利用普通 B+ Tree 索引快速定位。


4. 不满足最左前缀

索引:

(name,age)

查询:

WHERE age = 20;

通常无法利用联合索引进行高效定位。


5. OR

如果 OR 两边:

索引情况不同

可能导致优化器选择:

全表扫描

但:

OR ≠ 一定索引失效

6. 返回数据太多

例如:

WHERE sex = 1;

返回:

50%的数据

大量:

回表

优化器可能:

直接全表扫描

二十六、最终知识链

现在把前面学的索引知识全部串起来:

MySQL索引
    ↓
B+ Tree
    ↓
利用有序性快速查询
    ↓
┌──────────────┐
↓              ↓
聚簇索引      二级索引
↓              ↓
完整行        索引列+主键
               ↓
            需要完整数据
               ↓
              回表

然后:

联合索引
   ↓
(A,B,C)
   ↓
最左匹配

查询字段全部在索引:

覆盖索引
   ↓
避免回表

如果:

函数

隐式类型转换

LIKE前导%

不满足最左前缀

可能:

无法有效利用B+ Tree快速定位

即使:

可以利用索引

还要:

优化器计算成本

最终:

索引成本低
↓

走索引


全表扫描成本低
↓

全表扫描

🎤 面试回答

问:

哪些情况可能导致 MySQL 索引失效?

答:

常见情况包括对索引列使用函数、发生隐式类型转换、LIKE 使用前导 %、联合索引不满足最左前缀,以及查询返回数据量过大、索引选择性太低等。

另外不能简单认为 OR、!=NOT IN 一定导致索引失效。MySQL 优化器最终会根据统计信息和执行成本选择执行计划。

所以索引失效的核心,一方面是 SQL 是否还能利用 B+ Tree 的有序性快速定位数据,另一方面是优化器判断走索引的成本是否低于其他执行方式。


🎯 面试追问

Q1:什么叫索引失效?

答:

不是:

索引坏了

而是:

SQL无法有效利用索引快速定位

或者

优化器认为使用索引成本太高

↓

最终没有选择该索引

Q2:为什么索引列使用函数可能影响索引?

答:

因为普通索引:

按照原始字段值组织

而:

函数(字段)

比较的是:

计算后的结果

可能无法直接利用普通索引原来的有序性进行快速定位。


Q3:LIKE 哪种情况通常可以走索引?

答:

LIKE '张%'

通常可以利用:

前缀范围

进行索引查询。


Q4:为什么 LIKE '%张%' 通常不能快速利用普通索引?

答:

因为:

左侧前缀不确定

无法直接确定 B+ Tree 中:

从哪里开始查

Q5:OR 一定导致索引失效吗?

答:

不一定

两边都有合适索引时:

MySQL 可能使用:

Index Merge

等方式。


Q6:!= 一定导致索引失效吗?

答:

不一定

真正需要考虑:

返回数据量

索引选择性

回表成本

优化器成本估算

Q7:为什么字段有索引,MySQL 还是可能全表扫描?

答:

因为:

MySQL优化器会计算成本

如果:

走索引
+
大量回表

比:

全表扫描

还贵:

优化器可能:

直接全表扫描

Q8:为什么性别字段单独建立索引效果可能不好?

答:

因为:

值非常少

↓

大量数据拥有相同值

↓

选择性低

查询可能返回:

大量数据

导致:

大量回表

优化器可能:

不使用索引

Q9:怎么判断 SQL 到底有没有走索引?

答:

EXPLAIN SQL;

重点看:

key
type
rows
Extra

等信息。


⚠️ 易错点

1. 不要背“出现 OR 就失效”

错误:

OR
=
一定索引失效

正确:

看两边索引情况

+

看优化器执行计划

2. 不要背“!= 一定失效”

错误:

!=
=
一定不用索引

正确:

可能使用

也可能因为返回数据太多

↓

优化器选择全表扫描

3. 有索引不代表一定使用

存在索引

≠

最终一定走索引

因为:

优化器会计算成本

4. 使用索引也不代表一定快

如果:

索引查询出几百万条

↓

几百万次相关回表/数据页访问

可能:

比全表扫描还慢

所以:

索引不是越多越好

也不是走索引就一定快

❓ 自测

  1. 什么叫索引失效?

  2. 索引失效是不是索引坏了?

  3. 为什么索引列使用函数可能影响索引?

  4. YEAR(create_time)=2026 可以怎么优化?

  5. 什么是隐式类型转换?

  6. VARCHAR 字段为什么最好使用字符串参数查询?

  7. LIKE '张%' 为什么通常可以利用索引?

  8. LIKE '%张' 为什么通常无法利用普通索引快速定位?

  9. LIKE '%张%' 呢?

  10. 联合索引不满足最左前缀会怎样?

  11. 不满足最左前缀是不是代表 MySQL 绝对不会扫描这个索引?

  12. OR 一定导致索引失效吗?

  13. != 一定导致索引失效吗?

  14. NOT IN 一定导致索引失效吗?

  15. 为什么 sex 字段有索引也可能不走?

  16. 什么是索引选择性?

  17. 什么样的字段选择性比较高?

  18. 什么样的字段选择性比较低?

  19. 为什么大量回表可能导致优化器放弃索引?

  20. 有索引是不是一定走索引?

  21. 走索引是不是一定比全表扫描快?

  22. MySQL 根据什么决定是否使用索引?

  23. 怎么确认一条 SQL 实际使用了哪个索引?

  24. 索引失效和 B+ Tree 的有序性有什么关系?


评论