type
status
date
slug
summary
tags
category
icon
password

MySQL索引(二) — 索引为何失效

前言

虽然我们在MySQL索引(一) — 理解索引中深入理解了索引的概念和存储情况,但索引也绝非是万能的。
如果查询语句导致了索引失效,而不得不走全表扫描时,即使查询到的结果无误,但是效率却会大大降低。

1.对索引使用左或左右模糊匹配

当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx% 这两种方式都会造成索引失效。
具体原因就是因为索引 B+ 树 是按照 “索引值” 进行有序排序存储的,因此只能根据前缀进行比较。
例如面对这个索引 idx_id_price
notion image
假设我们要查找的 id 字段前缀为 "0001" 的数据,即id LIKE '0001%',就可以通过索引的形式一步步往下匹配找到。
但如果使用的是 id LIKE '%001' 的形式,查询到的结果就有可能包含 '0001', '0012', '0019' 等等情况,是无法得知从哪个索引开始比较的,所以只能全表扫描来查询。

2.联合索引不满足最左匹配原则

详见MySQL索引(一) — 理解索引的联合索引部分

3.WHERE 子句中的 OR

在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。
这是因为 OR 的含义就是两个只要满足一个即可,因此只有一个条件列是索引列是没有意义的,只要有条件列不是索引列,就会进行全表扫描。

4.对索引使用函数

有时候我们会用一些 MySQL 自带的函数来得到我们想要的结果,这时候要注意了,如果查询条件中对索引字段使用函数,就会导致索引失效。
比如下面这条语句查询条件中对 name 字段使用了 LENGTH 函数,执行计划中的 type=ALL,代表了全表扫描:
notion image
但为什么对索引使用函数,就无法走索引了呢?
因为索引保存的是索引字段的原始值,而不是经过函数计算后的值,自然就没办法走索引了。
不过,从 MySQL 8.0 开始,索引特性增加了函数索引,即可以针对函数计算后的值建立一个索引,也就是说该索引的值是函数计算后的值,所以就可以通过扫描索引来查询数据。
举个例子,我通过下面这条语句,对 LENGTH(name) 的计算结果建立一个名为 idx_name_length 的索引。
然后我再用下面这条查询语句,这时候就会走索引了。
notion image

5.对索引进行表达式计算

在查询条件中对索引进行表达式计算,也是无法走索引的。
比如,下面这条查询语句,执行计划中 type = ALL,说明是通过全表扫描的方式查询数据的:
notion image
但是,如果把查询语句的条件改成 where id = 10 - 1,这样就不是在索引字段进行表达式计算了,于是就可以走索引查询了。
notion image
为什么对索引进行表达式计算,就无法走索引了呢?
原因跟对索引使用函数差不多。
因为索引保存的是索引字段的原始值,而不是 id + 1 表达式计算后的值,所以无法走索引,只能通过把索引字段的取值都取出来,然后依次进行表达式的计算来进行条件判断,因此采用的就是全表扫描的方式。

6.对索引进行隐式类型转换

如果索引字段是字符串类型,但是在条件查询中,输入的参数是整型的话,你会在执行计划的结果发现这条语句会走全表扫描。
但是如果索引字段是整型类型,查询条件中的输入参数即使字符串,是不会导致索引失效,还是可以走索引扫描。
但为什么会这样呢?
要明白这个原因,首先我们要知道 MySQL 的数据类型转换规则是什么?就是看 MySQL 是会将字符串转成数字处理,还是将数字转换成字符串处理。
在《MySQL实战45讲》中,有一个简单的测试方式,就是通过 SELECT “10” > 9 的结果来知道MySQL 的数据类型转换规则是什么:
  • 如果规则是 MySQL 会将自动「字符串」转换成「数字」,就相当于 SELECT 10 > 9,这个就是数字比较,所以结果应该是 1;
  • 如果规则是 MySQL 会将自动「数字」转换成「字符串」,就相当于 SELECT "10" > "9",这个是字符串比较,字符串比较大小是逐位从高位到低位逐个比较(按ascii码) ,那么"10"字符串相当于 “1”和“0”字符的组合,所以先是拿 “1” 字符和 “9” 字符比较,因为 “1” 字符比 “9” 字符小,所以结果应该是 0。
测验结果如下:
notion image
上面的结果为 1,说明 MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字,然后再进行比较
而 MySQL 把字符串转化为数字的操作,是通过增加一个函数 CAST 来实现的,也就是说 MySQL 对索引使用了函数,而在前面也讲过了,这是会导致素银失效的一种情况。

7.不同字段值对比

从一张表中查询出一些值,然后根据这些值去其他表中筛选数据,这个业务也是实际项目中较为常见的场景。但这也是索引失效的一种可能情况。
如下 SQL 所示:
notion image

8.反向范围操作

一般来说,如果SQL属于正向范围查询,例如>、<、between、like、in...等操作时,索引是可以正常生效的,但如果SQL执行的是反向范围操作,例如NOT IN、NOT LIKE、IS NOT NULL、!=、<>...等操作时,就会出现问题,例如:
上述 SQL 的意思很简单,也就是查询 age 是/不是 18, 20, 23 的数据,这里是基于二级索引字段 age 查询的,但会走索引吗?来看看结果:
notion image
notion image
结果也很明显,使用 NOT 关键字做反向范围查询时,并不会走索引,索引此时失效了,但是做正向范围查询时,索引依旧有效。
不过也并非所有的正向范围操作都会走索引,例如IS NULL就不会走,它的反向操作:IS NOT NULL同样不会走。

9. 走索引在 MySQL 中,当走扫描的行数过多

在 MySQL 中,当走索引扫描的行数超过表行数的 30% 时,MySQL 会默认放弃索引查询,转而使用全表扫描的方式检索数据,因此这种情况下走索引的顺序磁盘 IO,反而不一定有全表的随机磁盘 IO 快。

参考资料:
Tuwilt
Tuwilt
言辞不必有力