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


    一个问题

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

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


    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;

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

    下面给大家捋一捋,以下试验基于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 |
    +----+-------------+-------+------------+-------+---------------+-----------+---------+------+-------+----------+--------------------------+

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

    CloumnMeaning
    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+树是怎样的呢?看图,一图胜百字

    对于索引来说只不过比单值索引多了几列,而这些索引列全都出现在索引树上。对于复合索引,存储引擎会首先根据第一个索引列排序,如上图我们可以单看第一个索引列,如,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 |
    +----+-------------+-------+------------+-------+---------------+-----------+---------+------+-------+----------+--------------------------+

    按照我们刚刚讲的索引的 左匹配原则,这个查询应该没有有效用上我们建的索引 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~~~~

  • 相关阅读:
    【NOIP2009】【Vijos1752】潜伏者
    【NOIP2008】【Vijos1493】传纸条
    【NOIP2007】【Vijos1378】矩阵取数游戏
    【NOIP2006】【Luogu1063】能量项链
    【NOIP2004】【Luogu1091】合唱队形
    【NOIP2004】【Luogu1089】津津的储蓄计划
    【NOIP2005】【Luogu1052】过河
    【NOIP2004】【Luogu1090】合并果子
    【NOI2002】【Luogu1196】银河英雄传说(并查集带边权)
    【POJ3190】Stall Reservations
  • 原文地址:https://www.cnblogs.com/CNYYGJ/p/12677690.html
Copyright © 2020-2023  润新知