MySQL单表访问方法

数据库 / 2022-07-25

先来模拟一张表,建表语句如下:

create table single_table (
	id int not null auto_increment,
	key1 varchar(100),
	key2 int,
	key3 varchar(100),
	key_part1 varchar(100),
	key_part2 varchar(100),
	key_part3 varchar(100),
	common_field varchar(100),
	primary key (id),
	key idx_key1 (key1),
	unique key idx_key2 (key2),
	key idx_key3 (key3),
	key idx_key_part (key_part1, key_part2, key_part3)
) engine=innodb charset=utf8;

这个 signle_table 表有1个聚簇索引和4个二级索引,分别是:

  • 为 id 列建立的聚簇索引;
  • 为 key1 列建立的 idx_key1 二级索引;
  • 为 key2 列建立的 idx_key2 二级索引;
  • 为 key3 列建立的 idx_key3 二级索引;
  • 为 key_part1、key_part2、key_part3 列建立的 idx_key_part 二级索引,这也是一个联合索引。

10.1 访问方法(access method)的概念

对于单个表的查询来说,查询的执行方式大致分为下边两种:

  • 使用全表扫描进行查询
    这种执行方式很好理解,就是把表的每一行记录都扫描一遍,把符合搜索条件的记录加入到结果集就完事了。
    不管什么查询都可以使用这种方式进行,当然这也是最笨的执行方式。
  • 使用索引进行查询
    因为直接使用全表扫描的方式执行查询要遍历好多记录,所以代价可能太大了。如果查询语句中的搜索条件可以使用到某个索引,那直接使用索引来执行查询可能会加快查询执行的时间。使用索引来执行查询的方式五花八门,又可以细分为许多种类:
    • 针对主键或唯一二级索引的等值查询;
    • 针对普通二级索引的等值查询;
    • 针对索引列的范围查询;
    • 直接扫描整个索引;

MySQL执行语句的方式称为 访问方法 或者 访问类型。同一个查询语句可能可以使用多种不同的访问方法来执行,虽然最后的查询结果都是一样的,但是执行的时间可能相差甚远,下面将详细介绍 访问方法 的具体内容。

10.2 const

有的时候我们可以通过主键列来定位一条记录,比如下方这个查询语句:

select * from signle_table where id = 143;

MySQL 会直接利用主键值在聚簇索引中定位对应的用户记录,就像这样:

或是下面这个语句:

select * from single_table where key2 = 3841;

那么执行过程可以简化为如下:

上面两个语句,一个是通过主键值确定唯一记录,一个是通过唯一二级索引确定唯一记录。对于这样通过主键或唯一二级索引列与常数的等值比较来定位一条记录的方式被称为 const ,意思是常数级别的,代价忽略不计。

这种方法只能在主键列或者唯一二级索引列的一个常数进行等值比较时才有效

如果主键或者唯一二级索引是由多个列组成的,索引中的每一个列都需要与常数进行等值比较,这个 const 访问方法才有效(这是因为只有该索引中全部采用等值比较才可以定位唯一的一条记录)

对于唯一二级索引来说,查询该列为 null 值的情况比较特殊,必如这样:

select * from single_table where key2 is null

因为唯一二级索引并不会限制 null 的数量,所以上诉语句查询出来的结果可能存在多个,也就意味着不可以使用 const 访问方法来执行。

10.3 ref

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

select * from single_table where key1 = "abc";

对于这个查询,我们可以走二级索引找到 id 值,然后再回表到聚簇索引中查找完整的用户记录。由于普通二级索引不会限制索引列值的唯一性,所以可能会找到多条对应的记录,也就是说使用二级索引来执行查询的代价取决于等值匹配到二级索引记录的数量。

如果匹配的记录较少,则回表的代价还是比较低的,所以MySQL可能选择使用索引而不是全表扫描的方式来执行查询。

如果一个列有二级索引,并不会一定会走索引。因为如果使用二级索引导致多次回表的代价超过全表扫描,这就得不偿失了。
MySQL的回表逻辑是,查到一条记录就立即回表,然后接着继续往下查找。

MySQL的设计者就把这种搜索条件为普通二级索引列与常数等值比较,采用二级索引来执行查询的访问方法称为:ref
我们看一下采用 ref 访问方法执行查询的图示:

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

  • 二级索引列值为 null 的情况
    不论是普通的二级索引,还是唯一二级索引,它们的索引列对包含 null 值的数量并不限制,所以我们采用 key is null 这种形式的搜索条件最多只能使用 ref 的方法,而不是 const 的访问方法。
  • 对于某个包含多个索引列的二级索引,只要最左边的连续索引列是与常数的等值比较就可能才用 ref 的访问方法,比如:
select * from single_table where key_part1 = 'good like';
select * from single_table where key_part1 = 'good like' and key_part2 = "le";
select * from single_table where key_part1 = 'good like' and key_part2 = "l" and key_part2 = "x";
但是如果最左边的连续索引列并不全是等值比较的话,它的访问方法就不能称为 ref 了,比如:
select * from single_table where key_part1 = "good like" and key_part2 > "l";

10.4 ref_or_null

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

select * from single_table from key1 = 'abc' and key1 is null;

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

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

10.5 range

我们之前介绍的几种访问方法都是等值比较才可能使用到,这一节我们来面对更复杂的查询,比如:

select * from single_table where key2 in (1438, 6328) or (key2 >= 38 and key2 <=79);

我们当然还可以使用全表扫描的方式来执行这个查询,不过也可以使用 二级索引+回表 的方式执行,如果采用后者的话,那么此时的搜索条件就不只是要求等值了,而是索引列需要匹配某个或某些范围的值。

对于这种利用索引进行范围匹配的访问方法被称为 range。

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

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

我们可以把那种索引列等值匹配的情况称为 单点区间,另一种就是 连续范围区间

10.6 index

看下边这个查询:

select key_part1, key_part2, key_part3 from single_table where key_part2 = 'abc';

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

  • 它的查询列表只有三个列:key_part1,key_part2,key_part3,而索引 idx_key_part 又包含这三个列。
  • 搜索条件中只有 key_part2 列。这个列也包含在索引 idx_key_part 中。

也就是说我们可以直接通过遍历 idx_key_part 索引的叶子节点的记录来比较 key_part2 = 'abc' 这个条件是否成立。
由于只需要查询三个字段,而 idx_key_part 索引是包含了这三个字段的,所以这个查询不需要回表操作。对于这样采用遍历二级索引记录的执行方式称为:index。

10.7 all

最直接的查询执行方式就是我们已经提了无数遍的全表扫描,对于 innodb 表来说也就是直接扫描聚簇索引。
将这种使用全表扫描执行查询的方式称为:all

10.8 注意事项

10.8.1 重温二级索引+回表

一般情况下只能利用单个二级索引执行查询,比如:

select * from single_table where key1 = 'abc' and key2 > 1000;

查询优化器会识别到这个查询中的两个搜索条件:

  • key1 = ‘abc’
  • key2 > 1000

优化器一般会根据 single_table 表的统计数据来判断到底使用哪个条件到对应的二级索引中查询扫描的行数会更少,然后选择扫描行数较少的条件到对应的二级索引中查询。
然后将从该二级索引中查询到的结果经过回表得到完整的用户记录后再根据其余的 where 条件过滤记录。
一般来说,等值查找比范围查找需要扫描的行数更少(也就是 ref 的访问方法一般 range 好,但这也不是绝对的,也可能采用 ref 访问方法的哪个索引列的值为特定的行数特别多),所以这里假设优化器决定使用 idx_key1 索引进行查询,那么整个查询过程可以分为以下两个步骤:

  1. 步骤1:使用二级索引定位记录的阶段,也就是根据条件 key1 = 'abc'idx_key1 索引代表的B+树中找二级索引记录。
  2. 步骤2:回表阶段,也就是根据上一步找到的记录的主键值进行回表操作,也就是在聚簇索引中找到完整的用户记录,再根据key2 > 1000到完整的用户记录中继续过滤。最后将符合过滤条件的记录返回给用户。
值得注意的是,因为二级索引的节点中的记录只包含索引列和主键值,所以在步骤1中使用`idx_key1`索引慬查询时只会用到与`key1`列有关的搜索条件,其余条件,比如`key2 > 1000`这个条件在步骤1中是用不到的,只有在步骤2完成回表后才能继续针对完整的用户记录继续过滤。

10.8.2 明确range访问方法使用的范围区间

其实对于 B+ 树来说,只要索引列和常数使用 `=, <=>, in, not in, is null, is not null, >, <, >=, <=, between, !=, like`操作符,就可以产生一个所谓的 **区间**。 

like操作符比较特殊,只有匹配完整字符串或者匹配字符串前缀时才可以利用到索引。

in操作符的效果和若干个等值匹配操作符`=`之间用`or`连接起来是一样的,也就是说会产生多个单点区间,比如下边这两个语句的效果是一样的:

select * from single_table where key2 in (1, 2);

select * from single_table where key2 = 1 or key2 = 2;

当我们想使用 range 访问方法来执行一个查询语句时,重点就是找出该查询可用的索引以及这些索引对应的范围区间。

下边分两种情况看一下怎么由 and 或 or组成的复杂搜索条件中提取正确的范围区间。

10.8.2.1 所有搜索条件都可以使用某个索引的情况

有的时候每个搜索条件都可以使用到某个索引,比如:

select * from single_table where key2 > 100 and key2 > 200;

这个查询中的搜索条件都可以使用到key2的索引,也就是说每个搜索条件都对应着一个 idx_key2 的范围区间。这两个小的搜索条件使用and连接起来,也就是取两个范围的交集,在我们使用range访问方法执行查询时,使用的idx_key2索引的范围区间的确定过程如下图所示:

key2 > 100key2 > 200交集当然就是 key2 > 200了。

我们再看一下使用or将多个搜索条件连接在一起的情况:

select * from signle_table where key2 > 100 or key2 > 200;

则使用range访问方法执行查询时,使用的idx_key2索引的范围区间如下:

所以范围区间就是 (100, +∞️)

10.8.2.2 有的搜索条件无法使用索引的情况

select * from single_table where key2 > 100 and common_field = "abc";

上面这个查询语句中能利用到的索引只有 idx_key2 一个,而 idx_key2 这个索引记录中又不包含 common_field 这个字段,所以在使用二级索引 idx_key2 定位记录的阶段用不到 common_field = ‘abc’ 这个条件。

这个条件只能在回表操作完成之后才会被使用到,而 范围区间 是为了到索引中取记录中提出的概念,所以在确定范围区间的时候不需要考虑common_filed = 'abc'这个条件,我们在为某个索引确定范围区间的时候只需把用不到相关索引的搜索条件替换成TRUE即可。

之所以把用不到索引的搜索条件替换为TRUE,是因为我们不打算使用这些条件进行在该索引上进行过滤,所以不管索引的记录满不满足这些条件,我们都把它们取出来,待到之后回表的时候再使用它们过滤。

把上面的语句,替换之后就是这样的:

select * from single_table where key2 > 100 and true;

简化之后就是:

select * from single_table where key2 > 100;

也就是说上边那个查询使用idx_key2的范围区间就是:(100, +∞️)

再看一下使用or的情况:

select * from single_table where key2 > 100 or common_field = "abc";

同理简化为:

select * from single_table where key2 > 100 or true;

or一真即全真,所以继续简化为:

select * from single_table where true;

这也就说明如果我们强制使用 idx_key2 执行查询的话,对应的范围区间就是(-∞️, +∞️),也就是需要将该二级索引的全部记录进行回表,这个代价肯定比直接全表扫描都大。也就是说一个使用到索引的搜索条件和没有使用该索引的搜索条件使用 or 连接起来后是无法使用该索引的。

10.8.2.3 复杂搜索条件下找出范围匹配的区间

有的查询的搜索条件可能特别复杂,光是找出范围匹配的各个区间就挺烦的,比如:

select *
from single_table
where (key1 > 'xyz' and key2 = 748)
	or (key1 < 'abc' and key1 > 'lmn')
	or (key1 like '%suf' and key1 > 'zzz' and (key2 < 8000 or common_field = 'abc')));

这个语句有点复杂,层层嵌套,按着下边这个套路分析一下:

  • 首先查看where子句中的搜索条件都涉及到了哪些列,哪些列可能使用到索引;
    这个查询的搜索条件涉及到了key1、key2、common_field这三个列,然后key1列有普通的二级索引idx_key1,key2列有唯一二级索引idx_key2。
  • 对于那些可能用到的索引,分析它们的范围区间。

假设我们使用idx_key1执行查询
我们需要把那些用不到该索引的搜索条件暂时移除掉,也就是直接把它们替换城TRUE。上边的查询除了有关key2common_field列不能使用到idx_key1索引外,key1 like '%suf'也使用不到索引,所以把这些搜索条件替换为TRUE之后就是这样的:

(key1 > 'xyz' and true) or
(key1 < 'abc' and key1 > 'lmn') or
(true and key1 > 'zzz' and (true or true))

继续简化:

(key1 > 'xyz') or
(key1 < 'abc' and key1 > 'lmn') or
(key1 > 'zzz')

替换掉永远为TRUE或FALSE的条件
因为符合 key1 < 'abc' and key1 > 'lmn' 永远为FALSE,所以上边的搜索条件可以简化为这样:

(key1 > 'xyz') or (key1 > 'zzz')

通过上面的表达式,很明显,区间就是 key1 > 'xyz'

也就是说:这么多搜索条件的查询语句如果使用idx_key1索引执行查询的话,需要把满足 key1 > 'xyz' 的二级索引记录全部都取出来,然后拿着这些记录的id再进行回表,得到完整的用户记录后再使用其它搜索条件进行过滤。

假设我们使用idx_key2执行查询
把那些用不到该索引的搜索条件全部使用TRUE替换掉,替换结果如下:

(true and key2 = 748) or
(true and true) or
(true and true and (key2 < 8000 or true))

根据异或关系,去掉永远为true或false的条件

(key2 = 748) or (true and true and true)

那么

(key2 = 748) or true

显而易见,整个搜索条件就是true,区间范围也就是从负无穷到正无穷,也就是需要扫描 idx_key2 二级索引的所有记录,然后再回表。这种情况肯定是得不偿失的,所以这种情况不会使用 idx_key2 索引。

10.8.3 索引合并

在前边说过的MySQL在一般情况下执行一个查询时最多只会用到单个二级索引,但凡事都有个特殊情况。
在这些特殊情况下也可能在一个查询中使用到多个二级索引,MySQL的设计者把这种使用到多个二级索引来完成一次查询的执行方法称为:index merge,具体的索引合并算法有下边三中。

10.8.3.1 Intersection合并

intersection翻译过来就是交集。这里是说某个查询可以使用多个二级索引,将从多个二级索引中查询到的结果交集,比如:

select * from single_table where key1 = 'a' and key3 = 'b';

假设这个查询使用intersection合并的方式执行的话,那么这个过程如下:

  • idx_key1二级索引对应的B+树取出key1 = 'a'的相关记录;
  • idx_key3二级索引对应的B+树取出key3 = 'b'的相关记录;
  • 二级索引的记录由 索引列+主键 构成,所以我们可以计算这两个结果集中 id 值的交集;
  • 按照上一步生出的id值列表进行回表操作,也就是从聚簇索引中把指定 id 值的完整用户记录取出来,返回给用户;
为啥不直接使用`idx_key1`或者`idx_key3`只根据某个搜索条件去读取一个二级索引,然后回表后在过滤另一个搜索条件呢?

只读取一个二级索引的成本:

  • 按照某个搜索条件读取一个二级索引;
  • 根据从该二级索引得到的主键值进行回表,然后再过滤其它条件

读取多个二级索引之后再取交集成本:

  • 按照不同的搜索条件分别读取不同的二级索引;
  • 将从多个二级索引得到的主键值取交集,然后进行回表

虽然读取多个二级索引比读取一个二级索引更消耗性能,但是读取二级索引的操作是顺序IO,而回表操作是随机IO,所以如果只读取一个二级索引时需要回表的记录数特别多,而读取多个二级索引之后之后取交集的记录数非常少

MySQL在某些特定的情况下才可能会使用到Interseciton索引合并:

  • 情况1: 二级索引列是等值匹配的情况,对于联合索引来说,在联合索引中的每个列都必须是等值匹配,不能出现只匹配部分列的情况;
  • 情况2: 主键列可以是范围匹配
    比如说下面这个查询可能用到主键和idx_key1进行intersection索引合并的操作: ^9409d8
select * from single_table where id > 100 and key1 ='a';
二级索引的用户记录是 `索引列+主键` 构成的,二级索引列的值相同的记录可能会有好多条,这些索引列的值相同的记录又是按照 `主键` 的值进行排序的。

所以重点来了,之所以在二级索引列都是**等值匹配**的情况下才可能使用 `Intersection` 索引合并,是因为 ==只有在这种情况下根据二级索引查询出来的结果集是按照主键值排序的。==

intersection索引合并会把从多个二级索引中查询出的主键值求交集,如果从各个二级索引中查询到的结果集本身就是按主键排好序的,那么求交集的过程就很简单了。

下面举个例子,看看是如何根据求交集的。假设某个查询使用intersection索引合并的方式从idx_key1idx_key2这两个索引中获取到的主键值分别是:

  • idx_key1中获取到已排好序的主键值:1、3、5
  • idx_key2中获取到已排好序的主键值:2、3、4

那么求交集的过程就是这样:
先从两个结果集中分别取一个最小值出来;

  1. 如果两者相等,则加入最终结果集;
  2. 如果key1的元素大于key2的元素,则key2往后移动一位;
  3. 如果key1的元素小于key2的元素,则key1向后移动一位;
    重复上诉过程,直到两个结果集遍历结束。

Talk is cheap. Show me the code.

下面用代码来模拟这个合并过程:

lst1 = [1, 3, 5]
lst2 = [2, 3, 4]
i = j = 0
ret = []

while i != len(lst1) and j != len(lst2):
	v1 = lst1[i]
	v2 = lst2[j]
	if v1 == v2:
		ret.append(v1)
		i += 1
		j += 1
	elif v1 > v2:
		j += 1
	else:
		i += 1

print(ret)

如果你知道合并排序,对这个比较过程应该不会陌生。

这个比较的时间复杂度是 O(n) ,所以很快的。
但是如果从各个二级索引中查询出的结果集并不是按照主键排序的话,那就先把各个结果集按主键值排序然后再交集,这样一来就非常耗时了。

按照有序的主键值去回表取记录有个专有名词,叫:Rowid Ordered Retrieval,简称ROR。

不仅是多个二级索引之间可以采用intersection索引合并,索引合并也可以有聚簇索引参与,也就是上面的[[10.单表访问方法#^9409d8|情况二]]

为什么主键就可以范围匹配?

举个例子,比如下面这个查询:

select * from single_table where key1 = 'a' and id > 100;

假设这个查询可以采用Intersection索引合并,我们理所当然的以为这个查询会分别按照 id > 100key1 = 'a'这两个条件拿到两个结果集,然后对其做交集。Hold on,Hold on… 想一想,我们又是利用二级索引,又是求交集的,其实最终目的无非就是为了拿到二级索引上的主键值。

别忘了,二级索引本身就带有主键值的,所以在 idx_key1 中获取到主键值的基础上直接运用条件 id > 100 过滤就行,而不用用上面的求交集的算法了。所以涉及主键的搜索条件只不过是为了从别的二级索引得到的结果集中过滤记录罢了,是不是等值不重要。

如果用代码演示,就可以是这样的:

# id为主键值,key1是该列的值
key1_lst = [{"id": 1, "key1": 1}, {"id": 2, "key1": 2}]
# 主键搜索条件:id > 1

ret = []

for item in key1_lst:
    if item["id"] > 1:
        ret.append(item["id"])

print(ret)  # [2]

当然,上边说的两种情况只是发生intersection索引合并的必要条件,不是充分条件。也就是说即使满足了这两种情况,也不一定会发生intersection索引合并。
这个就得看优化器如何选择了,优化器只有在单独根据搜索条件从某个二级索引中获取的记录数太多而导致回表开销太大时,才会来瞧一瞧这个备用方法,而且还得试着看看intersection索引合并后回表的记录数量是否大大减少,如果确实数量减少明显下才会使用intersection索引合并。

10.8.3.2 Union合并

我们在写查询语句时经常想把既符合A条件又符合B条件的记录取出来,我们说这些不同的搜索条件之间的关系是OR。有时候OR关系的不同搜索条件会使用到不同的索引,例如:

select * from single_table where key1 = 'a' or key3 = 'b';

Intersection是交集的意思,这适用于不同索引的搜索条件之间使用AND连接起来的情况;Union是并集的意思,适用于不同的搜索条件之间使用OR连接起来的情况。

Intersection类似,MySQL在某些特定的情况下才可能会使用到Union索引合并:

  • 情况一:二级索引列是等值匹配的。对于联合索引来说,在联合索引中的每个列都必须是等值匹配,不能出现只匹配部分列的情况。
  • 情况二:主键列可以是范围匹配;
  • 情况三:使用Intersection索引合并的搜索条件
    这个情况其实也比较好理解,就是搜索条件的某些部分使用了Intersection索引合并的方式得到的主键集合和其它方式得到的主键集合取交集,比如:
select *
from single_table
where key_part1 = 'a'
	and key_part2 = 'b'
	and key_part2 = 'c'
	or (key1 = 'a' and key3 = 'b');

优化器可能采用这样的方式来执行这个查询:

  • 先按照搜索条件key1 = 'a' and key3 = 'b'从索引 idx_key1idx_key3中使用Intersection索引合并的方式得到一个主键集合;
  • 再按照搜索条件 key_part1 = 'a' and key_part2 = 'b' and key_part3 = 'c'从联合索引idx_key_part中得到另一个主键集合;
  • 接着采用Union索引合并的方式把上述两个主键集合取并集,然会进行回表,将结果返回给用户。

当然,查询条件符合了这些情况同样也不一定就会采用Union索引合并。优化器只有在单独根据搜索条件从某个二级索引中获取的记录数比较少,通过Union索引合并后进行访问的代价比全表扫描更小时才好使用Union索引合并。

10.8.3.3 Sort-Union合并

Union 索引合并的使用条件太苛刻了,必须保证各个二级索引列在进行等值匹配的条件才可以被使用到,比方说下边这个查询就无法使用到 Union 索引合并:

select * from single_table where key1 < 'a' or key3 > 'z';

这是因为 key1 < 'a'idx_key1 索引中获取的二级索引记录的主键值不是排好序的,同理,key3 > 'z'idx_key3 索引中获取的二级索引记录的主键值也不是排好序的。

但是,这两个搜索条件对我们来说又非用不可,所以就可以这样:

  • 先根据 key1 < 'a' 条件从 idx_key1 二级索引中获取记录,并按照记录的主键值进行排序;
  • 再根据 key3 > 'z' 条件从 idx_key3 二级索引中获取记录,并按照记录的主键值进行排序;

那么现在,就是对两个有序集合做并集啦,之后再按照 Union 索引合方式执行的方式称为 Sort-Union 索引合并,这也很好记住,就是先排序(Sort)再合并(Union),与Union相比,就是多了一步排序的过程。

为什么有`Sort-Union`索引合并,而没有`Sort-Intersection`索引合并呢?

Sort-Union索引合并的适用场景是单独根据搜索条件从某个二级索引中获取的记录数量较少时,这样即时对这些二级索引记录按照主键值进行排序的成本也不会太高。
Intersection索引合并的适用场景是单独根据搜索条件从某个二级索引中的记录数太多,导致回表开销太大,合并后可以显著降低回表开销。而假如有Sort-Intersection,就需要为原本就比较多的记录进行排序操作,进而更加剧了开销。

索引合并注意事项

联合索引替代Intersection索引合并

select * from single_table where key1 = 'a' and key3 = 'v';

这个查询之所以可以用到intersection索引合并,是因为 key1key3 有索引,如果将key1key3建立联合索引,就可以直接搞定了,还少读了一棵 B+ 树。

不过得小心对`key3`列进行查询的业务场景,比如,单独查询`key3`列,就无法用到索引了,这样子就不得不为`key3`列单独加上索引。