暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MySQL 单表查询的底层实现(下)

一只可爱的小码农 2021-10-18
397

ref

有时候我们对某个普通的二级索引列与常数进行等值比较,比如这样:

  1. SELECT * FROM single_table WHERE key1 = 'abc';

对于这个查询,我们当然可以选择全表扫描来逐一对比搜索条件是否满足要求,我们也可以先使用二级索引找到对应记录的 id
值,然后再回表到聚簇索引中查找完整的用户记录。由于普通二级索引并不限制索引列值的唯一性,所以可能找到多条对应的记录,也就是说使用二级索引来执行查询的代价取决于等值匹配到的二级索引记录条数。如果匹配的记录较少,则回表的代价还是比较低的,所以 MySQL
可能选择使用索引而不是全表扫描的方式来执行查询。设计 MySQL
的大叔就把这种搜索条件为二级索引列与常数等值比较,采用二级索引来执行查询的访问方法称为: ref
。我们看一下采用 ref
访问方法执行查询的图示:


从图示中可以看出,对于普通的二级索引来说,通过索引列进行等值比较后可能匹配到多条连续的记录,而不是像主键或者唯一二级索引那样最多只能匹配1条记录,所以这种 ref
访问方法比 const
差了那么一丢丢,但是在二级索引等值比较时匹配的记录数较少时的效率还是很高的(如果匹配的二级索引记录太多那么回表的成本就太大了),跟坐高铁差不多。不过需要注意下边两种情况:

1、二级索引列值为 NULL
的情况,不论是普通的二级索引,还是唯一二级索引,它们的索引列对包含 NULL
值的数量并不限制,所以我们采用 key IS NULL
这种形式的搜索条件最多只能使用 ref
的访问方法,而不是 const
的访问方法。

2、对于某个包含多个索引列的二级索引来说,只要是最左边的连续索引列是与常数的等值比较就可能采用 ref
的访问方法,比方说下边这几个查询:

  1. SELECT * FROM single_table WHERE key_part1 = 'god like';

  2. SELECT * FROM single_table WHERE key_part1 = 'god like' AND key_part2 = 'legendary';

  3. SELECT * FROM single_table WHERE key_part1 = 'god like' AND key_part2 = 'legendary' AND key_part3 = 'penta kill';

但是如果最左边的连续索引列并不全部是等值比较的话,它的访问方法就不能称为 ref
了,比方说这样:

  1. SELECT * FROM single_table WHERE key_part1 = 'god like' AND key_part2 > 'legendary';

ref_or_null

有时候我们不仅想找出某个二级索引列的值等于某个常数的记录,还想把该列的值为 NULL
的记录也找出来,就像下边这个查询:

  1. SELECT * FROM single_demo WHERE key1 = 'abc' OR key1 IS NULL;

当使用二级索引而不是全表扫描的方式执行该查询时,这种类型的查询使用的访问方法就称为 ref_or_null
,这个 ref_or_null
访问方法的执行过程如下:

可以看到,上边的查询相当于先分别从 idx_key1
索引对应的 B+
树中找出 key1 IS NULL
key1='abc'
的两个连续的记录范围,然后根据这些二级索引记录中的 id
值再回表查找完整的用户记录。

range

我们之前介绍的几种访问方法都是在对索引列与某一个常数进行等值比较的时候才可能使用到( ref_or_null
比较奇特,还计算了值为 NULL
的情况),但是有时候我们面对的搜索条件更复杂,比如下边这个查询:

  1. SELECT * FROM single_table WHERE key2 IN (1438, 6328) OR (key2 >= 38 AND key2 <= 39);

我们当然还可以使用全表扫描的方式来执行这个查询,不过也可以使用 二级索引+回表
的方式执行,如果采用 二级索引+回表
的方式来执行的话,那么此时的搜索条件就不只是要求索引列与常数的等值匹配了,而是索引列需要匹配某个或某些范围的值,在本查询中 key2
列的值只要匹配下列3个范围中的任何一个就算是匹配成功了:

  • key2
    的值是 1438

  • key2
    的值是 6328

  • key2
    的值在 38
    和 79
    之间。

设计 MySQL
的大叔把这种利用索引进行范围匹配的访问方法称之为: range

小贴士:

此处所说的使用索引进行范围匹配中的 索引
 可以是聚簇索引,也可以是二级索引。

如果把这几个所谓的 key2
列的值需要满足的 范围
在数轴上体现出来的话,那应该是这个样子:

也就是从数学的角度看,每一个所谓的范围都是数轴上的一个 区间
,3个范围也就对应着3个区间:

  • 范围1: key2=1438

  • 范围2: key2=6328

  • 范围3: key2[38,39]
    ,注意这里是闭区间。

我们可以把那种索引列等值匹配的情况称之为 单点区间
,上边所说的 范围1
范围2
都可以被称为单点区间,像 范围3
这种的我们可以称为连续范围区间。法是分布式技术大师Lamport提出的,主要目的是通过这个算法,让参与分布式

index

如下查询:

  1. SELECT key_part1, key_part2, key_part3 FROM single_table WHERE key_part2 = 'abc'

由于 key_part2
并不是联合索引 idx_key_part
最左索引列,所以我们无法使用 ref
或者 range
访问方法来执行这个语句。但是这个查询符合下边这两个条件:

  • 它的查询列表只有3个列: key_part1
    key_part2
    key_part3
    ,而索引 idx_key_part
    又包含这三个列。

  • 搜索条件中只有 key_part2
    列。这个列也包含在索引 idx_key_part
    中。

也就是说我们可以直接通过遍历 idx_key_part
索引的叶子节点的记录来比较 key_part2='abc'
这个条件是否成立,把匹配成功的二级索引记录的 key_part1
, key_part2
, key_part3
列的值直接加到结果集中就行了。由于二级索引记录比聚簇索记录小的多(聚簇索引记录要存储所有用户定义的列以及所谓的隐藏列,而二级索引记录只需要存放索引列和主键),而且这个过程也不用进行回表操作,所以直接遍历二级索引比直接遍历聚簇索引的成本要小很多,设计 MySQL
的大叔就把这种采用遍历二级索引记录的执行方式称之为: index

all

最直接的查询执行方式就是我们已经提了无数遍的全表扫描,对于 InnoDB
表来说也就是直接扫描聚簇索引,设计 MySQL
的大叔把这种使用全表扫描执行查询的方式称之为: all


文章转载自一只可爱的小码农,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论