单表访问方式
访问方法的概念
平时我们写的 SQL 语句,只是一个词法层面上的表示,止于 MySQL 背后做了写什么事情,我们可能并不关心。在 MySQL 中,执行 SQL 语句的方式,称为访问方法或者访问类型。不同的访问类型,对应的性能开销是不一样的,逻辑推测我们自己写的 SQL 语句大概率会以哪种方式执行,也是一种必备的能力。本篇会介绍不同访问类型的性能差异(重点在时间复杂度上)。
const
翻译过来就是“常量”,这种级别的 SQL 查询效率是最高的,通常发生在主键或者唯一二级索引的等值查询上,进行一种精确的查询。
select * from table where id = 333;
select * from table where unique_index = 222;
上述两条 SQL 语句都是const
级别的查询,性能最好。但是对于唯一二级索引
,如果等值条件是null
的话,则可能进行范围性的查找,就不是const
级别的了:
select * from table where unique_index is null;
ref
const
之所以快,是因为等值查询的时候,最多有一条数据。而ref
比const
查询性能稍差,因为进行等值查询的时候,会进行范围性的扫描:
select * from table where index_1 = 'abc';
由于并没有进行唯一约束,所以值等于abc
的记录,可能有很多条,需要对叶子节点中的二级索引记录沿着链表进行范围性的扫描,然后进行回表查询操作。
这里关于回表查询操作,多说一下。是命中了一条二级索引之后,立马拿到主键值进行回表查询,而不是等所有的二级索引记录全部查询出来之后,再进行回表操作。
因此,可以很好的解释,为什么is null
,在普通二级索引当中,不是const
了,因为它可能会查询出很多的结果集,虽然它是等值查询,所以它的访问类型是ref
。
ref_or_null
其实就是在ref
的基础上,多扫描了一些值为null
的二级索引记录而已:
select * from table where index_1 = 'abc' or index_1 is null;
值为null的记录会被放在索引的最左边。
range
“范围性”的扫描,不过指的不是在叶子节点的记录上进行范围性的扫描,是查询中有很多的边界条件:
select * from table where index_2 int (33,44) or (index_2 >= 50 and index_2 <= 94);
边界条件有哪些呢?[33,33]、[44,44]、[50,94]。
注意与前面的区别,前面的访问类型,边界条件都只有一个,而这个是要包含多个。(ref_or_null 是个特例)
index
“全索引记录扫描”,通常会出现在覆盖索引上,查询条件为联合索引的某个字段,但是无法通过这个字段去缩小查询范围,只能扫描全部的索引记录,进行筛选操作:
select index_1,index_2,index_3 from table where index_2 = "abc";
按照 index_1,index_2,index_3 的顺序建立了联合索引,由于 index_2 是在 index_1等值的基础上才能保持有序,所以无法通过 index _2 来缩小查询范围,自然会进行“全表扫描”。但是,由于输出字段都在同一棵B+树
上,而二级索引记录的大小要比真实用户记录小很多,查询效率自然高很多。
ALL
全表扫描,从聚簇索引最左边的叶子节点,向右一直扫描。
MRR
前面我们说过,二级索引会进行回表操作,每取出一个二级索引记录中的主键值,就去聚簇索引中查询用户记录。由于二级索引记录的主键是没有顺序的,也就是可能会产生大量的随机I/O。因此,引入了MRR(多范围随机读取)。
工作机制也很简单,取出部分的二级索引记录,将其中的主键值进行顺序排列,然后进行统一的回表操作。
了解一下就好了,有对应的参数可以控制是否启用。
索引合并
一般情况下,SQL 语句之后选择一个索引,但是在某些条件成立的时候,可以选择多个索引执行查询操作。
1、intersection 索引合并
简单来说,就是在不同的二级索引记录中取主键值的交集,进行回表操作:
select * from table where index_1 = 'a' and index_2 = 'b';
无论选择哪个索引,过程都是一样的,这里就不多做赘述。我们来看一下索引合并的过程。
我们都知道,二级索引,其实都是联合索引,当二级索引的字段值相同时,会按照主键值进行排序。即,相同的二级索引记录,主键值是有序的。(这里的相同是二级索引字段值相同)
上述的 SQL 语句中,有两个等值查询条件,但是会走不同的索引,在各自的B+树
上进行范围性查找操作,取出相同的主键值,进行回表操作。
intersection索引合并
的操作要求索引记录对应的主键值必须是有序的,原因如下:
- 有序集合可以更方便的找出相同主键值
- 有序的主键值可以减少随机I/O发生的概率
其实intersection索引合并
的关键点就两个:交集
、主键值有序
。
还要需要注意的就是,
select * from table where index_1 = 'a' and id > 9000;
这并不会扫描真正的聚簇索引,因为 index_1 的索引记录里面就包含了主键id,等值查询就代表主键id是有序的,而刚好 index_1 索引记录中有 id 列,就不需要进行所谓的索引合并
了。
2、Union 索引合并
和intersection索引合并
差不多,也是要求主键值必须是有序的,不过它取的是交集
:
select * from table where index_1 = 'a' or index_2 = 'b';
抓住关键点,分析主键值是否有序
、是否是并集操作
。
等值查询,并不能代表索引记录对应的id值就是有序的,比如联合索引:
select * from table index_1 = 'a' and index_3 = 'b';
假设 index_3,index_4 创建了联合索引,但是 index_3 值相等的索引记录对应的主键值并不是有序的,主键值是否有序,依赖于前一个字段是否等值。
3、Sort - Union 索引合并
对于Union 索引合并
来说,主键值必须有序,这样的条件太苛刻了,对于下列语句:
select * from table where index_1 < 'a' and index_2 > 'c';
很明显,不能走Union 索引合并
,但是两个索引条件又比较诱人,怎么办?既然是无序的,那就全部取出来,排序不就好了嘛,然后再做去重工作,回表查询。(MySQL 的确也是这么多的)
很简单,对吧。
2025/02/02
writeBy kaiven