mysql 联接查询算法之Batched key access

2020-12-07  本文已影响0人  尹楷楷

Batched key access (BKA)

BKA算法集结了 INLJ、BNLJ、MRR 算法的特性。它用到了INLJ的内部表索引减少关联匹配的次数;又使用到了BNLJ的join buffer,用以暂存外表连接数据减少访问内部表;还用到了MRR的收集主键rowid后排序再回表查询,随机IO转顺序IO;可以把BKA看做是INLJ算法的加强版!

我们知道INLJ使用内部表索引进行关联能够大幅提升查询效率,但是当查询中涉及到其它字段时需要回表查询。通过辅助索引进行联接后需要回表,这里需要大量的随机I/O操作。若能优化随机I/O,那么就能极大的提升Join的性能。为此,MySQL 5.6(MariaDB 5.3)开始支持Batched Key Access Join算法(简称BKA),该算法通过常见的空间换时间,随机I/O转顺序I/O,以此来极大的提升Join的性能。

在说明Batched Key Access Join前,首先介绍下MySQL 5.6的新特性mrr——multi range read。
这篇有对MRR进行说明 https://www.jianshu.com/p/3d9b9b4ea186

理解了 MRR 性能提升的原理,我们就能理解 MySQL 在 5.6 版本后开始引入的 Batched Key Acess(BKA) 算法了。我们知道 INLJ 算法执行的逻辑是:从驱动表一行行地取出 join 条件值,再到被驱动表去做 join。也就是说,对于被驱动表来说,每次都是匹配一个值。这时,MRR 的优势就用不上了。那怎么才能一次性地多传些值给被驱动表呢?方法就是,从驱动表里一次性地多拿些行出来,一起传给被驱动表。既然如此,我们就把驱动表的数据取出来一部分,先放到一个临时内存。这个临时内存不是别人,就是 join_buffer。

我们知道 join_buffer 在 BNL 算法里的作用,是暂存驱动表的数据。但是在 NLJ 算法里并没有用。那么,我们刚好就可以复用 join_buffer 到 BKA 算法中。NLJ 算法优化后的 BKA 算法的流程,整个过程如下所示:

MySQL联接查询算法(NLJ、BNL、BKA、HashJoin)

外表-->外表join buffer --> 内表index-->收集主键进行排序(mrr)--> 内表

对于多表join语句,当MySQL使用索引访问第二个join表的时候,使用一个join buffer来收集第一个操作对象生成的相关列值。BKA构建好key后,批量传给引擎层做索引查找。key是通过MRR接口提交给引擎的,这样,MRR使得查询更有效率。

如果外部表扫描的是主键,那么表中的记录访问都是比较有序的,但是如果联接的列是非主键索引,那么对于表中记录的访问可能就是非常离散的。因此对于非主键索引的联接,Batched Key Access Join算法将能极大提高SQL的执行效率。BKA算法支持内联接,外联接和半联接操作,包括嵌套外联接。

Batched Key Access Join算法的工作步骤如下:
1、 将外部表中相关的列放入Join Buffer中。
2、
3、批量的将Key(索引键值)发送到Multi-Range Read(MRR)接口。
4、Multi-Range Read(MRR)通过收到的Key,根据其对应的ROWID进行排序,然后再进行数据的读取操作(随机IO转顺序IO)。
5、返回结果集给客户端。

在MySQL 5.6中默认关闭BKA(MySQL 5.7默认打开),必须将optimizer_switch系统变量的batched_key_access标志设置为on。BKA使用MRR,因此mrr标志也必须打开。目前,MRR的成本估算过于悲观。因此,mrr_cost_based也必须关闭才能使用BKA。

SHOW VARIABLES LIKE '%optimizer_switch%'

index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=on,condition_fanout_filter=on,derived_merge=on

以下设置启用BKA:

SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';

官方给出的BKA和MRR性能基准测试


image.png

如何确保使用到BKA并达到了优化目的?

Batched Key Access Join算法的本质上来说还是Simple Nested-Loops Join算法,其发生的条件为内部表上有索引,并且该索引为非主键,并且联接需要访问内部表主键上的索引。这时Batched Key Access Join算法会调用Multi-Range Read(MRR)接口,批量的进行索引键的匹配和主键索引上获取数据的操作,以此来提高联接的执行效率,因为读取数据是以顺序磁盘IO而不是随机磁盘IO进行的。

因为BKA算法的本质是通过MRR接口将非主键索引对于记录的访问,转化为根据ROWID排序的较为有序的记录获取,所以要想通过BKA算法来提高性能,不但需要确保联接的列参与match的操作(联接的列可以是唯一索引或者普通索引,但不能是主键),还要有对非主键列的search操作。例如下列SQL语句:

explain select a.gender, b.dept_no from employees a, dept_emp b where a.birth_date=b.from_date;
+----+-------------+-------+------------+------+----------------+----------------+---------+-----------------------+--------+----------+----------------------------------------+
| id | select_type | table | partitions | type | possible_keys  | key            | key_len | ref                   | rows   | filtered | Extra                                  |
+----+-------------+-------+------------+------+----------------+----------------+---------+-----------------------+--------+----------+----------------------------------------+
|  1 | SIMPLE      | b     | NULL       | ALL  | NULL           | NULL           | NULL    | NULL                  | 331570 |   100.00 | NULL                                   |
|  1 | SIMPLE      | a     | NULL       | ref  | idx_birth_date | idx_birth_date | 3       | employees.b.from_date |     62 |   100.00 | Using join buffer (Batched Key Access) |
+----+-------------+-------+------------+------+----------------+----------------+---------+-----------------------+--------+----------+----------------------------------------+
2 rows in set, 1 warning (0.00 sec)

列a.gender是表employees的数据,但不是通过搜索idx_birth_date索引就能得到数据,还需要回表访问主键来获取数据。因此这时可以使用BKA算法。但是如果联接不涉及针对主键进一步获取数据,内部表只参与联接判断,那么就不会启用BKA算法,因为没有必要去调用MRR接口。比如search的主键(a.emp_no),那么肯定就不需要BKA算法了,直接覆盖索引就可以返回数据了(二级索引有主键值)。

explain select a.emp_no, b.dept_no from employees a, dept_emp b where a.birth_date=b.from_date;
+----+-------------+-------+------------+------+----------------+----------------+---------+-----------------------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys  | key            | key_len | ref                   | rows   | filtered | Extra       |
+----+-------------+-------+------------+------+----------------+----------------+---------+-----------------------+--------+----------+-------------+
|  1 | SIMPLE      | b     | NULL       | ALL  | NULL           | NULL           | NULL    | NULL                  | 331570 |   100.00 | NULL        |
|  1 | SIMPLE      | a     | NULL       | ref  | idx_birth_date | idx_birth_date | 3       | employees.b.from_date |     62 |   100.00 | Using index |
+----+-------------+-------+------------+------+----------------+----------------+---------+-----------------------+--------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

在EXPLAIN输出中,当Extra值包含Using join buffer(Batched Key Access)且类型值为ref或eq_ref时,表示使用BKA。

什么样的场景适合使用BKA join?

加了辅助索引的字段所在表作为join的内表,索引无法覆盖,select需要回表

使用hint

mysql> EXPLAIN  SELECT /*+ BKA(b)*/ 
    a.gender,
    b.* 
FROM
    employees a,
    dept_emp b 
WHERE
    a.birth_date = b.from_date;
+----+-------------+-------+------------+------+-----------------------+-----------------------+---------+------------------------+--------+----------+----------------------------------------+
| id | select_type | table | partitions | type | possible_keys         | key                   | key_len | ref                    | rows   | filtered | Extra                                  |
+----+-------------+-------+------------+------+-----------------------+-----------------------+---------+------------------------+--------+----------+----------------------------------------+
|  1 | SIMPLE      | a     | NULL       | ALL  | NULL                  | NULL                  | NULL    | NULL                   | 300043 |   100.00 | NULL                                   |
|  1 | SIMPLE      | b     | NULL       | ref  | `from_date`,`dept_no` | `from_date`,`dept_no` | 3       | employees.a.birth_date |     47 |   100.00 | Using join buffer (Batched Key Access) |
+----+-------------+-------+------------+------+-----------------------+-----------------------+---------+------------------------+--------+----------+----------------------------------------+
2 rows in set (0.03 sec)

上一篇下一篇

猜你喜欢

热点阅读