MySQL优化系列13-MySQL8.0 表连接方法简介

备注:测试数据库版本为MySQL 8.0

一. Nested Loop Join算法

1.1 普通的Nested Loop Join算法

将外层表的结果集作为循环的基础数据,然后循环从该结果集每次一条获取数据作为下一个表的过滤条件去查询数据,然后合并结果。如果有多个表join,那么应该将前面的表的结果集作为循环数据,取结果集中的每一行再到下一个表中继续进行循环匹配,获取结果集并返回给客户端。

for each row in t1 matching range {
  for each row in t2 matching reference key {
     for each row in t3 {
      if row satisfies join conditions,
      send to client
    }
  }
 }

我们可以看到Nested loop join(以下简称NLJ),对于小表驱动大表且大表上关联列存在索引的情况下,是非常快的。但是对于两个大表进行join,NLJ的需要循环非常多的次数,此时性能就会很差劲了。

1.2 Block Nested-Loop算法

MySQL BNL算法原本只支持内连接,现在已支持外连接和半连接操作,包括嵌套外连接。

BNL算法原理:将外层循环的行/结果集存入join buffer,内存循环的每一行数据与整个buffer中的记录做比较,可以减少内层循环的扫描次数

举个简单的例子:外层循环结果集有1000行数据,使用NLJ算法需要扫描内层表1000次,但如果使用BNL算法,则先取出外层表结果集的100行存放到join buffer, 然后用内层表的每一行数据去和这100行结果集做比较,可以一次性与100行数据进行比较,这样内层表其实只需要循环1000/100=10次,减少了9/10。

伪代码:

for each row in t1 matching range {
   for each row in t2 matching reference key {
    store used columns from t1, t2 in join buffer
    if buffer is full {
      for each row in t3 {
         for each t1, t2 combination in join buffer {
          if row satisfies join conditions,
          send to client
        }
        }
       empty buffer
     }
   }
 }

 if buffer is not empty {
    for each row in t3 {
     for each t1, t2 combination in join buffer {
       if row satisfies join conditions,
       send to client
      }
   }
 }

1.3 Batched Key Access 算法

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

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

Batched Key Access Join算法的工作步骤如下:

  1. 将外部表中相关的列放入Join Buffer中。
  2. 批量的将Key(索引键值)发送到Multi-Range Read(MRR)接口。
  3. Multi-Range Read(MRR)通过收到的Key,根据其对应的ROWID进行排序,然后再进行数据的读取操作。
  4. 返回结果集给客户端。

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

使用BKA时,join_buffer_size的值定义了对存储引擎的每个请求中批量密钥的大小。缓冲区越大,对连接操作的右侧表的顺序访问就越多,这可以显着提高性能。

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

二.Hash Join

在8.0.18之前,MySQL只支持NestLoopJoin算法,最简单的就是Simple NestLoop Join,MySQL针对这个算法做了若干优化,实现了Block NestLoop Join,Index NestLoop Join和Batched Key Access等,有了这些优化,在一定程度上能缓解对HashJoin的迫切程度。

NestLoopJoin算法简单来说,就是双重循环,遍历外表(驱动表),对于外表的每一行记录,然后遍历内表,然后判断join条件是否符合,进而确定是否将记录吐出给上一个执行节点。从算法角度来说,这是一个M*N的复杂度。HashJoin是针对equal-join场景的优化,基本思想是,将外表数据load到内存,并建立hash表,这样只需要遍历一遍内表,就可以完成join操作,输出匹配的记录。

三.表连接实例

从Extra可以看到默认是hash join,原来默认情况下BKA算法是关闭的,关联列有索引且数据量不大,调整后,默认走的是BKA的算法。

mysql> explain select ename,dname from dept d inner join emp e on d.deptno = e.deptno;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                      |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | d     | NULL       | ALL  | PRIMARY       | NULL | NULL    | NULL |    4 |   100.00 | NULL                                       |
|  1 | SIMPLE      | e     | NULL       | ALL  | FK_DEPTNO     | NULL | NULL    | NULL |   14 |    33.33 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

mysql> 
mysql> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
Query OK, 0 rows affected (0.00 sec)

mysql> 
mysql> explain select /*+ BKA(d,e) */ename,dname from dept d inner join emp e on d.deptno = e.deptno; 
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                      |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | d     | NULL       | ALL  | PRIMARY       | NULL | NULL    | NULL |    4 |   100.00 | NULL                                       |
|  1 | SIMPLE      | e     | NULL       | ALL  | FK_DEPTNO     | NULL | NULL    | NULL |   14 |    33.33 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

3.1 Nest Loop Join

如何走出最普通的Nest Loop Join

mysql> explain format=tree select /*+ no_bka(e, d) */ename,dname from dept d inner join emp e on d.deptno = e.deptno\G  
*************************** 1. row ***************************
EXPLAIN: -> Nested loop inner join  (cost=4.52 rows=19)
    -> Table scan on d  (cost=0.65 rows=4)
    -> Index lookup on e using FK_DEPTNO (deptno=d.deptno)  (cost=0.62 rows=5)

1 row in set (0.00 sec)

如何走出BKA(关联列有索引)

-- 是否加hint,都是走BKA算法
mysql> explain format=tree select ename,dname from dept d inner join emp e on d.deptno = e.deptno\G                    
*************************** 1. row ***************************
EXPLAIN: -> Batched key access inner join
    -> Batch input rows
        -> Table scan on d  (cost=0.65 rows=4)
    -> Multi-range index lookup on e using FK_DEPTNO (deptno=d.deptno)

1 row in set (0.00 sec)

mysql> explain format=tree select /*+ bka(e, d) */ename,dname from dept d inner join emp e on d.deptno = e.deptno\G   
*************************** 1. row ***************************
EXPLAIN: -> Batched key access inner join
    -> Batch input rows
        -> Table scan on d  (cost=0.65 rows=4)
    -> Multi-range index lookup on e using FK_DEPTNO (deptno=d.deptno)

1 row in set (0.00 sec)

如何走出BNL(关联列没有索引)
尝试了很多场景,均未走出BNL,mysql的hints的优先级是低于索引的优先级,所以很多时候即便加了hints也依旧无效。

-- 非等值连接居然也用hash join(8.0.18 后尽可能的使用hash join已经支持非等值连接了)
mysql> explain select ename,dname from dept d left join emp e on d.deptno != e.deptno ; 
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                      |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | d     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    4 |   100.00 | NULL                                       |
|  1 | SIMPLE      | e     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   14 |   100.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

-- 加了hints也没用
mysql> explain format=tree /*+ bnl(d, e)*/select ename,dname from dept d left join emp e on d.deptno = e.deptno where e.deptno=10\G
*************************** 1. row ***************************
EXPLAIN: -> Inner hash join (no condition)  (cost=2.30 rows=0)
    -> Filter: (e.deptno = 10)  (cost=1.65 rows=1)
        -> Table scan on e  (cost=1.65 rows=14)
    -> Hash
        -> Filter: (d.deptno = 10)  (cost=0.65 rows=1)
            -> Table scan on d  (cost=0.65 rows=4)

1 row in set (0.00 sec)

-- 删除emp和dept表的key
alter table emp drop foreign key fk_deptno;
alter table dept drop primary key;
alter table emp drop key fk_deptno;


3.2 hash join

8.0.18 后尽可能的使用hash join已经支持非等值连接了,这点太给力了。

代码:

-- 走hash join
explain format=tree 
select e.*
  from emp e
 inner join dept d
 on e.deptno = d.deptno\G

-- 加了hints,依旧走hash join
explain format=tree 
select /*+ bnl(e, d)*/e.*
  from emp e
 inner join dept d
 on e.deptno = d.deptno\G

-- 使用了no_bnl,就不走hash join了,改为走nest loop join
explain format=tree 
select /*+ no_bnl(e, d)*/e.*
  from emp e
 inner join dept d
 on e.deptno = d.deptno\G

-- 非等值连接也可以使用hash join
explain format=tree 
select /*+ bnl(e, d)*/e.*
  from emp e
 inner join dept d
 on e.deptno > d.deptno\G

测试记录:

mysql> explain format=tree 
    -> select e.*
    ->   from emp e
    ->  inner join dept d
    ->  on e.deptno = d.deptno\G
*************************** 1. row ***************************
EXPLAIN: -> Inner hash join (e.deptno = d.deptno)  (cost=6.50 rows=6)
    -> Table scan on e  (cost=0.10 rows=14)
    -> Hash
        -> Table scan on d  (cost=0.65 rows=4)

1 row in set (0.00 sec)

mysql> explain format=tree 
    -> select /*+ bnl(e, d)*/e.*
    ->   from emp e
    ->  inner join dept d
    ->  on e.deptno = d.deptno\G
*************************** 1. row ***************************
EXPLAIN: -> Inner hash join (e.deptno = d.deptno)  (cost=6.50 rows=6)
    -> Table scan on e  (cost=0.10 rows=14)
    -> Hash
        -> Table scan on d  (cost=0.65 rows=4)

1 row in set (0.00 sec)

mysql> explain format=tree 
    -> select /*+ no_bnl(e, d)*/e.*
    ->   from emp e
    ->  inner join dept d
    ->  on e.deptno = d.deptno\G
*************************** 1. row ***************************
EXPLAIN: -> Nested loop inner join  (cost=7.25 rows=6)
    -> Table scan on d  (cost=0.65 rows=4)
    -> Filter: (e.deptno = d.deptno)  (cost=0.29 rows=1)
        -> Table scan on e  (cost=0.29 rows=14)

1 row in set (0.00 sec)

mysql> 
mysql> explain format=tree 
    -> select /*+ bnl(e, d)*/e.*
    ->   from emp e
    ->  inner join dept d
    ->  on e.deptno > d.deptno\G
*************************** 1. row ***************************
EXPLAIN: -> Filter: (e.deptno > d.deptno)  (cost=6.50 rows=19)
    -> Inner hash join (no condition)  (cost=6.50 rows=19)
        -> Table scan on e  (cost=0.18 rows=14)
        -> Hash
            -> Table scan on d  (cost=0.65 rows=4)

1 row in set (0.00 sec)

mysql> 

参考:

  1. https://dev.mysql.com/doc/refman/8.0/en/nested-join-optimization.html
  2. https://www.cnblogs.com/wanbin/p/9592943.html
  3. https://blog.csdn.net/ddhe9527/article/details/103659977
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
mysql-connector-java-8.0.jar是MySQL官方提供的JDBC驱动程序,用于与Java应用程序连接和操作MySQL数据库。下面是关于mysql-connector-java-8.0.jar的一些信息。 mysql-connector-java-8.0.jar是一个Java Archive文件,以.jar为扩展名。它包含了与MySQL数据库交互所需的类和方法。我们可以将该驱动程序添加到Java项目的classpath中,以便可以在项目中使用MySQL数据库。 mysql-connector-java-8.0.jar是MySQL官方推荐的JDBC驱动程序版本。它支持MySQL数据库的最新功能和性能优化。该驱动程序遵循JDBC标准,提供了与数据库的连接、查询、更新等操作的方法。 使用mysql-connector-java-8.0.jar驱动程序进行数据库连接时,需要提供MySQL数据库的连接信息,包括数据库的主机名、端口号、数据库名称、用户名和密码等。通过创建一个数据库连接对象,可以使用驱动程序提供的方法与数据库进行交互。 mysql-connector-java-8.0.jar还支持连接池功能,可以提高数据库连接的性能和效率。连接池可以维护一组数据库连接,从中获取连接以处理请求,而不会频繁地创建和关闭连接。 使用mysql-connector-java-8.0.jar进行数据库操作需要导入相关的类和方法,并按照JDBC的API进行使用。可以执行SQL查询、插入、更新、删除等操作,并通过结果集获取查询结果。 总而言之,mysql-connector-java-8.0.jar是与MySQL数据库连接和操作的必备JDBC驱动程序。通过将其添加到Java项目中,我们可以方便地使用Java语言与MySQL数据库进行交互,实现对数据库的增删改查等操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值