where后的索引只能走一个?索引合并

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

Intersection 交集合并

某个查询可以使用多个二级索引,将从多个二级索引中查询到的结果取交集,比方说下边这个查询:

SELECT * FROM order_exp WHERE order_no = 'a' AND expire_time = 'b'

假设这个查询使用 Intersection 合并的方式执行的话,那这个过程就是这样的:

  1. 从 idx_order_no 二级索引对应的 B+树中取出 order_no= 'a'的相关记录。
  2. 从 idx_insert_time 二级索引对应的 B+树中取出 insert_time= 'b'的相关记录。

二级索引的记录都是由索引列 + 主键构成的,所以我们可以计算出这两个结果集中 id 值的交集。 再将id去重后统一回表取出数据。

走一个索引回表和走两个索引回表对比

首先,交集索引合并走了两次索引,但最耗时的回表操作依旧是一次。并且通过取交集的操作,可能会去除许多原本不符合条件的id,减少回表操作。

当节省的因为回表而造成的性能损耗比访问多个二级索引带来的性能损耗更多时,读取多个二级索引后取交集比只读取一个二级索引的成本更低。 优化器就会选择索引合并。

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

索引合并情况一:二级索引等值匹配(结果合并)

二级索引列是等值匹配的情况,对于联合索引来说,在联合索引中的每个列都必须等值匹配,不能出现只匹配部分列的情况。(即时最左边的列也无法使用)

索引合并的底层原理

索引合并的底层原理实际上是,在二级索引中将查到的id排好序。在排好序之后可以依照逻辑快速的取交集。

假设某个查询使用 Intersection 索引合并的方式从 idx_order_no 和 idx_expire_time 这两个二级索引中获取到的主键值分别是:

  • 从 idx_order_no 中获取到已经排好序的主键值:1、3、5
  • 从 idx_expire_time 中获取到已经排好序的主键值:2、3、4

那么求交集的过程就是这样:逐个取出这两个结果集中最小的主键值,如果两个值相等,则加入最后的交集结果中,否则丢弃当前较小的主键值,再取该丢弃的主键值所在结果集的后一个主键值来比较,直到某个结果集中的主键值用完了,时间复杂度是 O(n)。

但是如果从各个二级索引中查询出的结果集并不是按照主键排序的话,那就要先把结果集中的主键值排序完再来做上边的那个过程,就比较耗时了,因此就索引合并就不考虑这种二级索引无序操作了。

联合索引必须全部等值匹配才能合并

我们很多时候对联合索引的理解是这样的:

假设有一个联合索引a,b,c。那么就相当于建立了三个索引:

  1. a
  2. ab
  3. abc

这样理解在等值查询,或是范围查询时是没有问题的。但是假设我们仅仅用联合索引中的a列会出现一个问题,就是联合索引对应的因为后续还有b,c索引的原因而导致id是无序的。

这也就违背了我们索引合并的底层逻辑。因此我们必须将整个联合索引的所有列放到条件中进行等值匹配,才能保证我们的联合索引id是天然有序的。

范围二级索引为什么不行

这也很好理解了。范围二级索引也无法保证id的有序性。

索引合并情况而二:主键索引是范围匹配

如下面这个例子,也可以使用索引合并:

SELECT * FROM order_exp WHERE id > 100 AND order_no = 'a';

这样依旧是有机会执行索引合并的。因为我们的主键id天然有序,优化器会先得到order_no = 'a'的id,再在id > 100的区间中取交集后进行查询。

当然,再怎么说,索引合并是否执行,还得让优化器来判断。我们举的例子仅仅是一个合并的前提而已。

Union并集合并

我们在写查询语句时经常想把既符合某个搜索条件的记录取出来,也把符合另外的某个搜索条件的记录取出来,我们说这些不同的搜索条件之间是 OR 关系。 有时候 OR 关系的不同搜索条件会使用到不同的索引,比方说这样:

SELECT * FROM order_exp WHERE order_no = 'a' OR expire_time = 'b'

Intersection 是交集的意思,这适用于使用不同索引的搜索条件之间使用 AND 连接起来的情况;Union 是并集的意思,适用于使用不同索引的搜索条件之间使 用 OR 连接起来的情况。与 Intersection 索引合并类似,MySQL 在某些特定的情况下才可能会使用到 Union 索引合并 。

情况一:等值匹配

分析同 Intersection 合并

情况二:主键列可以是范围匹配

分析同 Intersection 合并

情况三:使用 Intersection 索引合并的搜索条件

比方说这个查询:

SELECT * FROM order_exp WHERE insert_time = 'a'AND order_status = 'b' AND expire_time = 'c' 
OR 
(order_no = 'a' AND expire_time = 'b');

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

先按照搜索条件 order_no = 'a' AND expire_time = 'b'从索引 idx_order_no 和 idx_expire_time 中使用 Intersection 索引合并的方式得到一个主键集合。

再按照搜索条件 insert_time = 'a' AND order_status = 'b' AND expire_time = 'c' 从联合索引 u_idx_day_status 中得到另一个主键集合。

采用 Union 索引合并的方式把上述两个主键集合取并集,然后进行回表操作, 将结果返回给用户。

Sort-Union 排序合并

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

SELECT * FROM order_exp WHERE order_no< 'a' OR expire_time> 'z

这是因为根据 order_no< 'a'从 idx_order_no 索引中获取的二级索引记录的主键值不是排好序的,根据 expire_time> 'z'从 idx_expire_time 索引中获取的二级索 引记录的主键值也不是排好序的。

我们知道,or的使用可能会导致我们任何索引都无法使用。但是 order_no< 'a'和 expire_time> 'z''这两个条件不用的话就会明显就是非常可惜的,所以我们可以这样:

  1. 先根据 order_no< 'a'条件从 idx_order_no 二级索引中获取记录,并按照记录的主键值进行排序
  2. 再根据 expire_time> 'z'条件从 idx_expire_time 二级索引中获取记录,并按照 记录的主键值进行排序
  3. 因为上述的两个二级索引主键值都是排好序的,剩下的操作和 Union 索引合并方式就一样了。

上述这种先按照二级索引记录的主键值进行排序,之后按照 Union 索引合并 方式执行的方式称之为 Sort-Union 索引合并,很显然,这种 Sort-Union 索引合并 比单纯的 Union 索引合并多了一步对二级索引记录的主键值排序的过程。

联合索引替代 Intersection 索引合并

SELECT * FROM order_exp WHERE order_no= 'a' And expire_time= 'z';

这个查询为什么我们优化器会选择索引合并的方式?idx_order_no 和 idx_expire_time 是两个单独的 B+树索引,要是把这两个列搞一个 联合索引,那直接使用这个联合索引就把事情搞定了,何必用啥索引合并呢?因此这也是我们的一个优化方向。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大将黄猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值