MySQL使用 like '%x'索引一定会失效吗?

在非覆盖索引场景下,大家知道MySQL索引有最左原则,所以通过 like '%xx%'查询的时候一定会造成索引失效(5.7版本覆盖索引可以走索引),一般采用like 'xx%'右边匹配的方式来索引。

但是这样一定会使用索引吗?答案是否定的,不一定会用。

在覆盖索引下, like '%xx%'和like 'xx%'均走索引。

创建实验用的SQL:

CREATE TABLE `user` (
  `ID_` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `AGE_` INT(11) DEFAULT NULL,
  `NAME_` VARCHAR(255) DEFAULT NULL,
  PRIMARY KEY (`ID_`),
  UNIQUE KEY `NAME_` (`NAME_`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4

#插入三条数据
INSERT INTO `user` VALUES ('1', '1', '测试1.1'), ('2', '2', '测试2.2'), ('3', '3', '测试3');

注意:下面3个例子是基于非覆盖索引场景下

案例1:like '%测试%'

EXPLAIN select * from user where NAME_ like '%测试';

EXPLAIN结果为type=ALL,key=null,说明全表扫描没有使用索引:

在这里插入图片描述

案例2 like '测试%'

EXPLAIN select * from user where NAME_ like '测试%';

EXPLAIN结果为type=ALL,key=null,rows=3检索出所有数据,说明全表扫描没有使用索引:

在这里插入图片描述

案例3 like '测试1%'

EXPLAIN select * from user where NAME_ like '测试1%';

从上面三个案例,可以看出:like 'xxxx%' 是不一定100%使用索引,是否使用索引与该条件数量和数据总量比例有关?

那么这是为什么呢?其实就是离散性的问题

非覆盖索引场景下,WHERE 条件中,like 'xttblog%', like '%xttblog%', like '%xttblog',三种方式查询方式,后两种方式对于索引是无效的,有索引也不会走索引。第一种'xttblog%'是不确定的,决定于列的离散型,理论上讲可以用到,如果发现离散情况特别差的情况下,查询优化器觉得走索引查询性能更差,还不如全表扫描。所以,关于 like 查询其实很多人也是有误解的!

离散型的好坏,决定着优化器是否走索引。离散性越高,选择性就越高,可以让mysql在查找时过滤掉更多的行。

在这里插入图片描述

你看这个例子,status 字段的索引离散型非常的差,如果此时搜索 status = 1 的数据,根节点判断的时候,结果是查询左子树,但是当在左子树第二层再进行判断的时候,因为左右分支都满足条件,所以很难抉择选择哪一个分支继续搜索,或者是把两个分支同时进行搜索。

如果是范围查询还好一点,因为所有的叶子节点都是有顺序的。我从最左边开始,一直遍历到不符合条件的第一条数据为止,把数据返回。这是 B+ 树的一个特点,有序性更强!

但是呢?由于离散型非常的差,优化器可能直接就选择不走索引了,因为优化器可能认为,走索引和全表扫描差不多。

所以,我们在创建索引时,一定要选择重复值较低的字段

离散型有一个计算公式:count(distinct col):count(col),离散型越高,选择型越好。


前面我们讲的都是在非覆盖索引的情况下的。接下来我们来看看覆盖索引的场景。

如下:表只有两个字段,一个是主键索引 id,另外一个是二级索引 name。

图片

案例4:like 'xxx%'

图片

Extra 里的 Using index 说明用上了覆盖索引。

案例5:like '%xxx'

图片

从执行计划的结果中,可以看到 key=index_name,也就是说用上了二级索引,而且从 Extra 里的 Using index 说明用上了覆盖索引。

这是为什么呢?

首先,这张表的字段没有非索引字段,所以 select * 相当于 select id,name,然后这个查询的数据都在二级索引的 B+ 树,因为二级索引的 B+ 树的叶子节点包含索引值+主键值,所以查二级索引的 B+ 树就能查到全部结果了,这个就是覆盖索引。

但是执行计划里的 type 是 index,这代表着是通过全扫描二级索引的 B+ 树的方式查询到数据的,也就是遍历了整棵索引树。

而案例4的执行计划中 type 是 range,表示对索引列进行范围查询,也就是利用了索引树的有序性的特点,通过查询比较的方式,快速定位到了数据行。

所以,type=range 的查询效率会比 type=index 的高一些。

为什么选择全扫描二级索引树,而不扫描全表(聚簇索引)呢?

因为二级索引树的记录东西很少,就只有索引列+主键值,而聚簇索引记录的东西会更多,比如聚簇索引中的叶子节点则记录了主键值、事务 id、用于事务和 MVCC 的回流指针以及所有的剩余列。

再加上,这个 select * 不用执行回表操作。

所以, MySQL 优化器认为直接遍历二级索引树要比遍历聚簇索引树的成本要小的多,因此 MySQL 选择了「全扫描二级索引树」的方式查询数据。

为什么这个数据表加了非索引字段,执行同样的查询语句后,怎么变成走的是全表扫描呢?

加了其他字段后,select * from t_user where name like "%xx"; 要查询的数据就不能只在二级索引树里找了,得需要回表操作才能完成查询的工作,再加上是左模糊匹配,无法利用索引树的有序性来快速定位数据,所以得在二级索引树逐一遍历,获取主键值后,再到聚簇索引树检索到对应的数据行,这样实在太累了。

所以,优化器认为上面这样的查询过程的成本实在太高了,所以直接选择全表扫描的方式来查询数据。

从这个思考题我们知道了,使用左模糊匹配(like '%xx')并不一定会走全表扫描,关键还是看数据表中的字段

如果数据库表中的字段只有主键+二级索引,那么即使使用了左模糊匹配,也不会走全表扫描(type=all),而是走全扫描二级索引树(type=index)。

再说一个相似,我们都知道联合索引要遵循最左匹配才能走索引,但是如果数据库表中的字段都是索引的话,即使查询过程中,没有遵循最左匹配原则,也是走索引扫描的,而且 type 也是为 index,比如下图:

图片

 

posted @ 2022-02-19 12:23  残城碎梦  阅读(818)  评论(0编辑  收藏  举报