一个问题

有一张表test,这张表除了主键id外,还有a,b,  c 三列

假设给这三个字段建一个复合索引 index_abc (a, b, c),问,下面几种查询中,哪种查询会用到索引 index_abc ?

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。


1. 查询一

select * from test where a > 1000 and b > 1000;

2. 查询二

select * from test where a > 1000 and c > 1000

3. 查询三

select * from test where b > 1000 and c > 1000;
左匹配原则,聚集索引,回表查询,索引覆盖 你真的懂了吗 Mysql 第1张

这是一个经典的面试题,由这个问题,我可以相关问你,什么是 左匹配原则?什么是 聚集索引?什么是 索引覆盖?什么是 回表

下面给大家捋一捋,以下试验基于MySQL5.7-InnoDB

左匹配原则

接着上面的问题,回到刚刚的三个查询上,首先,我们怎么知道查询有没有用到索引?有没有什么命令是可以帮助我们分析查询语句呢?答案当然是有的,那就 explain 命令

我们分别对上面的语句进行 explain,看看有哪些信息:

mysql> explain select * from test where a > 1000 and b > 1000;
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key       | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | test  | NULL       | range | index_abc     | index_abc | 4       | NULL | 5060 |    33.33 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+

mysql> explain select * from test where a > 1000 and c > 1000;
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key       | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | test  | NULL       | range | index_abc     | index_abc | 4       | NULL | 5060 |    33.33 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+

mysql> explain select * from test where b > 1000 and c > 1000;
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+-------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key       | key_len | ref  | rows  | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+-------+----------+--------------------------+
|  1 | SIMPLE      | test  | NULL       | index | NULL          | index_abc | 12      | NULL | 10120 |    11.11 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+-------+----------+--------------------------+

 

左匹配原则,聚集索引,回表查询,索引覆盖 你真的懂了吗 Mysql 第2张

我们可以看到,对查询语句执行 explain 后,返回了12列信息,各列说明如下:

Cloumn Meaning
id  查询标识符
select_type 查询类型
table 输出行的表
partitions 匹配的分区
type 联接类型,确切的说是一种数据库引擎查找表的一种方式
possible_keys 可以选择的可能索引,但不一定被查询实际使用
key 实际选择的索引
key_len 所选键的长度
ref 与索引相比的列
rows 估计要查询的列
filtered 按表条件筛选的行百分比
Extra 其他信息

通常分析sql语句,我们只关注type,possible_keys,key,rows

对三条查询语句进行explain后,我们发现:

  • where a > 1000 and b > 1000 和 where a > 1000 and c > 1000条件的查询 结果是一样的,其中type指明的索引查找方式为range,possible_keys 可能使用的索引为 index_abc,key 实际使用的索引为 index_abc
  • where b > 1000 and c > 1000 条件的查询中,type的值为index,possible_keys为NULL,key的值为 index_abc


上面的range 和 index有什么区别呢?

  • range:仅检索给定范围内的行,使用索引选择行
  • index:索引联接类型与 ALL 相同,只不过扫描索引树,有两种情况:
    • 如果索引是查询的 覆盖索引(后文有讲),并且可用于满足表中所需的所有数据,则仅扫描索引树。在这种情况下,"额外"(Extra)列表示使用索引。 仅索引扫描通常比全部扫描快,因为索引的大小通常小于表数据
    • 使用索引中的读取执行完整的表扫描,以按索引顺序查找数据行。使用索引不显示在"额外"列中,也就是说:如果不是覆盖索引,使用索引不显示在"额外"列中

换句话说,

range是使用了索引,并且能够在对应的索引树上使用快速查找的方法进行快速查找,是有范围的查找,使用了range,就一定用到了我们建的索引,而index只能是通过扫描整个索引树

上面也提到ALL,那么type还有哪几种比较常见的值呢?下面列举一下(具体其他类型值,看以参考官方文档):

  • system:该表只有一行 (= 系统表)。这是 const 联接类型的特殊情况
  • const:表示通过索引一次就找到了,因为只匹配一行数据,所以很快。如将主键置于where列表中,MySQL就能将该查询转换为一个常量表最多有一个匹配行,在查询开始时读取该行。由于只有一行,因此优化器的其余部分可以将该行中的列中的值视为常量。将主键或 UNIQUE 索引的所有部分与常量值进行比较时,将使用 const
  • eq_ref:唯一性索引扫描,对于前一表中的每一行组合,将从此表中读取一行,常见于主键或唯一索引扫描。除了system 和 const 类型之外,这是最佳联接类型
  • ref:非唯一性索引扫描,对于前一表中的每一行组合,将从此表中读取具有匹配索引值的所有行
  • ALL:将遍历全表以找到匹配的行

好,回到上面三条查询语句上,为什么where条件为a > 1000 and b > 1000 和 a > 1000 and c > 1000 的 type 是 range(用到索引), 而where条件为 b > 1000 and c > 1000 的 type 是 index 呢?这里面索引树(B+树)的构建方式及存储结构有关

那么复合索引B+树是怎样的呢?看图,一图胜百字

左匹配原则,聚集索引,回表查询,索引覆盖 你真的懂了吗 Mysql 第3张

左匹配原则,聚集索引,回表查询,索引覆盖 你真的懂了吗 Mysql 第4张

对于索引来说只不过比单值索引多了几列,而这些索引列全都出现在索引树上。对于复合索引,存储引擎会首先根据第一个索引列排序,如上图我们可以单看第一个索引列,如,1 1 4 15 18....他是单调递增的;如果第一列相等则再根据第二列排序,依次类推就构成了上图的索引树

以创建的索引 index_abc (a, b, c)为例,如上图所示,每个结点都有三个键值,从上往下分别对应这a,b,c三个索引列

构造索引树时,首先使用多列索引的第一列构建的索引树,以 index_abc (a, b, c) 为例就是优先使用a列构建,当b列值相等时再以c列排序

因此,索引的第一列也就是a列可以说是从左到右单调递增的,但我们看b列和c列并没有这个特性,它们只能在a列值相等的情况下这个小范围内递增,看上图的左下角的结点可理解这点

划重点:由于复合索引树建的时候就是按照当初你建立索引时(index_abc (a, b, c))对应索引列的顺序从左到右来建的,因此你使用的时候你也得按照从左到右的规则来用,这就是索引的 左匹配原则

所以为什么上面 where a > 1000 and b > 1000 和 where a > 1000 and c > 1000 条件查询的type是range,而 where b > 1000 and c > 1000 的type是index 你明白来吗?

回表,聚集索引

我们都知道,B+树有个特点就是,其叶子结点存的是关键字和数据,非叶子结点存的都是索引关键字,那么复合索引构造的B+树中,其叶子结点存的数据是什么呢?答案该条数据的主键值

划重点:也就是说,利用复合索引查找数据的流程是,先在复合索引的B+树上找到对应数据的主键值(ID,注:MyISAM的索引叶子节点存储记录指针),然后再根据这个主键(ID)值,到主键索引树(B+树)上查找这个ID所在的行记录(主键索引树的页子结点存储的关键字和对应的行记录数据),最后查找结束。这个查找流程操作也叫 回表查询

有没有注意到,B+树中,有的叶子结点存储的行记录,有点存储的是主键值

划重点

  • 叶子结点存储行记录的索引又叫 聚集索引,InnoDB必须要有,且只有一个聚集索引:
    • 如果定义了主键,则主键索引就是 聚集索引
    • 如果没有定义主键,则第一个not NULL unique列是聚集索引
    • 否则,InnoDB会创建一个隐藏的row-id作为聚集索引
  • 叶子结点存储主键值叫普通索引,也叫 非聚集索引


覆盖索引

还是上面的例子,我们再次看一下 where条件为 b > 1000 and c > 1000 的查询 explain后的信息

mysql> explain select * from test where b > 1000 and c > 1000;
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+-------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key       | key_len | ref  | rows  | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+-------+----------+--------------------------+
|  1 | SIMPLE      | test  | NULL       | index | NULL          | index_abc | 12      | NULL | 10120 |    11.11 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+-------+----------+--------------------------+

 

左匹配原则,聚集索引,回表查询,索引覆盖 你真的懂了吗 Mysql 第5张

按照我们刚刚讲的索引的 左匹配原则,这个查询应该没有有效用上我们建的索引 index_abc ,为什么key(实际使用到的索引)列却是 index_abc?这里就涉及到了 覆盖索引

什么是覆盖索引?覆盖索引 就是:SQL只需要通过索引就可以返回查询所需要的数据,而不必通过二级索引查到主键之后再去查询数据(即回表查询

不难理解,因为我们的test表本来就只有四个字段,id, a, b, c,其中(a, b, c)建立列索引,id又是主键,复合索引树的叶子结点存的就是主键值,所以 select * from test where b > 1000 and c > 1000 查找的数据通过复合索引树就可以全部得到,不需要回表,因此这里面用到了索引,这个索引树实际是什么索引的索引树呢?,当然是index_abc了,因为b, c 列包含在复合索引列中

为什么possible_keys列(可能使用到的索引)为NULL,因为搜索引擎找不到以b列开头的索引

所以,使用列索引覆盖,Extra列也就有列Using index


最后,为什么 a > 1000 and b > 1000 和 b > 1000 and a > 1000,explain的结果一样呢?

mysql> explain select * from test where a > 1000 and b > 1000;
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key       | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | test  | NULL       | range | index_abc     | index_abc | 4       | NULL | 5060 |    33.33 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+

mysql> explain select * from test where b > 1000 and a > 1000;
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key       | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | test  | NULL       | range | index_abc     | index_abc | 4       | NULL | 5060 |    33.33 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+

这就该我们mysql 查询优化器 干活了,mysql查询优化器会判断纠正这条sql语句该以什么样的顺序执行效率最高,最后才生成真正的执行计划。


至此,索引的左匹配原则,聚集索引,回表查询,覆盖索引就分享完了

如有不妥之处,欢迎指正,交流

 

点个赞再走呗~thxs~~~~

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄