MySQL的几种表关联算法

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介:

一、Multi-Range Read(MRR)

1、MRR优化原理

1)在没有使用MRR时,MySQL处理思路的伪代码

#SQL
select * from tb  where key_column=xx ;

#伪代码
for each row r in R do
    if r satisfy the where condition
        then output the tuple <r>

#处理流程        
1.根据索引过滤,获取到满足条件的记录的结果集<r>
2.遍历结果集通过主键进行回表,此时为离散扫描,返回客户端需要的字段信息
    

2)MRR算法实现伪代码处理流程

#SQL
select * from tb where key_column=xx ;

#伪代码
for each row r in R do
    if r satisfy the where condition
        then output the tuple <r> order by rowid


#处理流程
1.根据索引过滤获取到满足条件的记录<r>
2.将满足条件的记录放至read_rnd_buffer_size,并根据rowid进行排序,得到一个有序的结果集<r> order by rowid
3.遍历结果集通过主键进行回表查询,此时为顺序扫描,返回客户端满足条件的记录

2、MRR的特点

1)MRR算法主要是针对基于二级索引的过滤查询的优化,若过滤条件无索引则进行全表扫描。

2)MRR的特点就是将通过索引获取到满足条件的结果集(二级索引,主键索引)按照主键索引进行排序,将随机IO转换为顺序IO,从而再后续回表查询时带来性能上的提升。

3、MRR参数控制

mysql> show variables like 'optimizer_switch'\G

mrr=on                       //mrr优化,默认打开
mrr_cost_based=on            //mrr基于代价评估是否使用mrr算法,默认打开。MySQL优化器CBO一般会认为mrr资源消耗较大而放弃使用mrr,若需要使用mrr,可以将该参数打开。

mysql> set optimizer_switch='mrr=on,mrr_cost_based=off';   //开启mrr

4、示例

root@mysql57 15:39:  [db2]> explain select * from sbtest1 where k<10000 ;
+----+-------------+---------+------------+-------+---------------+-------+---------+------+------+----------+-----------------------+
| id | select_type | table   | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+---------+------------+-------+---------------+-------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | sbtest1 | NULL       | range | idx_k         | idx_k | 4       | NULL |  194 |   100.00 | Using index condition |
+----+-------------+---------+------------+-------+---------------+-------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

root@mysql57 15:39:  [db2]> set session optimizer_switch='mrr=on,mrr_cost_based=off';
Query OK, 0 rows affected (0.00 sec)

root@mysql57 15:39:  [db2]> explain select * from sbtest1 where k<10000 ;
+----+-------------+---------+------------+-------+---------------+-------+---------+------+------+----------+----------------------------------+
| id | select_type | table   | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                            |
+----+-------------+---------+------------+-------+---------------+-------+---------+------+------+----------+----------------------------------+
|  1 | SIMPLE      | sbtest1 | NULL       | range | idx_k         | idx_k | 4       | NULL |  194 |   100.00 | Using index condition; Using MRR |
+----+-------------+---------+------------+-------+---------------+-------+---------+------+------+----------+----------------------------------+
1 row in set, 1 warning (0.00 sec)

二、Simple Nested-Loop Join(SNLJ)

1、SNLJ原理

image

1)伪代码

##SQL
select * from tb_r join tb_s on tb_r.r=tb_s.s 

##伪代码
for each row r in R do          
    for each row s in S do      
        if r and s satisfy the join condition   
            then output the tuple <r,s>

2)处理流程

1.遍历驱动表满足条件的所有记录
2.对于驱动表的每一行记录,对被驱动表进行遍历,获取到满足join条件的所有记录<r,s>
3.根据满足join条件的结果集,通过主键回表扫描,获取需要字段

2、SNLJ特点

1)该算法下SQL执行资源消耗最大,对于扫描次数来讲,驱动表扫描1次,被驱动表扫描次数取决于驱动表记录数(对于驱动表中每行记录都要遍历被驱动表一次);对于SQL扫描记录数来讲,SQL执行扫描行数 = 驱动表行数 + 驱动表记录数 * 被驱动表记录数(笛卡尔积)

2)该算法主要是针对被驱动表关联字段无索引的情况,但是MySQL对该情况做了优化,利用join buffer并通过BNL算法减少对被驱动表的扫描次数

3、示例

##手动关闭BNL,数据库会使用SNLJ算法
root@mysql57 15:55:  [db2]> set session optimizer_switch='block_nested_loop=off';
Query OK, 0 rows affected (0.00 sec)

root@mysql57 15:55:  [db2]> explain select * from sbtest1 r join sbtest2 s on r.k=s.k;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | r     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  100 |   100.00 | NULL        |
|  1 | SIMPLE      | s     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  200 |    10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

##可以看到SQL扫描记录数=100+100*200=20100
# Time: 2020-04-14T16:02:13.707953+08:00
# User@Host: root[root] @ localhost []  Id:    24
# Query_time: 0.007069  Lock_time: 0.000140 Rows_sent: 47  Rows_examined: 20100
SET timestamp=1586851333;
select * from sbtest1 r join sbtest2 s on r.k=s.k;

三、Index Nested-Loop Join

1、INLJ原理

image

1)伪代码实现

##SQL
select * from tb_r join tb_s on tb_r.r=tb_s.s 

##伪代码
for each row r in R do
    lookupr in S index
    if found s == r
    then output the tuple <r,s>

2)处理流程

1.遍历满足过滤条件的驱动表中所有的记录
2.对于驱动表中每一条记录,通过索引进行关联查询,找到所有满足join条件的记录<r,s>
3.遍历结果集,通过主键回表扫描,获取需要的字段信息

2、INLJ特点

1)该算法主要针对被驱动表关联列有索引的情况

2)该算法基本上是MySQL在进行表关联时使用最多的算法,对SQL的扫描数据来讲,驱动表扫描次数为1,被驱动表扫描次数为0;对于SQL的扫描记录数来讲,SQL执行扫描行数 = 驱动表记录数 + 驱动表的记录 * 索引关联满足条件记录数

3、示例

##对于驱动表关联字段有索引的情况,默认使用IBLJ算法
root@mysql57 16:04:  [db2]> explain select * from sbtest1 r join sbtest2 s on r.k=s.k;
+----+-------------+-------+------------+------+---------------+-------+---------+---------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref     | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------+---------+---------+------+----------+-------+
|  1 | SIMPLE      | r     | NULL       | ALL  | NULL          | NULL  | NULL    | NULL    |  100 |   100.00 | NULL  |
|  1 | SIMPLE      | s     | NULL       | ref  | idx_k         | idx_k | 4       | db2.r.k |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+-------+---------+---------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)

##可以看到该算法下SQL扫描记录数=100+47(满足join条件记录数)
# Time: 2020-04-14T16:05:29.890558+08:00
# User@Host: root[root] @ localhost []  Id:    24
# Query_time: 0.000714  Lock_time: 0.000144 Rows_sent: 47  Rows_examined: 147
SET timestamp=1586851529;
select * from sbtest1 r join sbtest2 s on r.k=s.k;

四、Block Nested-Loop Join(BNL)

1、BNL原理

image

1)伪代码

##SQL
select * from tb_r join tb_s on tb_r.r=tb_s.s 

##伪代码
for each tuple r in R do
    store used columns as p from R in join buffer
    for each tuple s in S do
        if p and s satisfy the join condition
            then output the tuple <p,s>

2)处理流程

1.遍历满足过滤条件的驱动表中所有的记录(SQL查询的所有字段),并放入至join buffer
2.若所有驱动表满足条件记录放入join buffer,遍历被驱动表所有记录,获取满足join条件的记录结果集
3.若join buffer无法一次性存储全部驱动表记录,可分批读取记录至join buff,重复2步骤

2、BNL特点

1)BNL主要是针对被驱动表关联字段无索引时的优化,如果我们在执行计划中看到“Using join buffer (Block Nested Loop)”说明被驱动表表表关联字段缺少索引或索引失效无法有效利用索引。

2)该算法是对SNLJ算法的优化,并且可将该算法BNL提升至INLJ进行优化。对SQL的扫描数据来讲,驱动表扫描次数为1,被驱动表扫描次数为 驱动表记录大小 / join_buffer_size ;对于SQL的扫描记录数来讲,SQL执行扫描行数 = 驱动表记录数 + (驱动表记录/join_buffer_size) * 被驱动表记录数

3)在一定的程度上,提高join_buffer_size的大小是可以提高使用BNL算法SQL的执行效率,但是join_buffer_size是会话连接上去就会提前进行分配的,若SQL为N个表join,那其分配的join buffer大小为 join_buffer_size * (N-1),所以生产环境中我们不可对该参数设置过大。

3、相关参数

mysql> show variables like 'optimizer_switch'\G
block_nested_loop=on                       //BNL优化,默认打开
mysql> set optimizer_switch='block_nested_loop=on';   //开启BNL

4、示例

##打开BNL算法
root@mysql57 16:07:  [db2]> set session optimizer_switch='block_nested_loop=on';
Query OK, 0 rows affected (0.00 sec)

##可以看到执行计划中使用了BNL算法
root@mysql57 16:07:  [db2]> explain select * from sbtest1 r join sbtest2 s on r.k=s.k;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
|  1 | SIMPLE      | r     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  100 |   100.00 | NULL                                               |
|  1 | SIMPLE      | s     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  200 |    10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

##该算法下SQL扫描记录数=100+1*200
# Time: 2020-04-14T16:08:33.345173+08:00
# User@Host: root[root] @ localhost []  Id:    24
# Query_time: 0.002777  Lock_time: 0.000144 Rows_sent: 47  Rows_examined: 300
SET timestamp=1586851713;
select * from sbtest1 r join sbtest2 s on r.k=s.k;


##手动调整join_buffer_size的大小
root@mysql57 16:08:  [db2]> show variables like '%join_buffer_size%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| join_buffer_size | 262144 |
+------------------+--------+
1 row in set (0.00 sec)

root@mysql57 16:11:  [db2]> set session join_buffer_size=1024;
Query OK, 0 rows affected (0.00 sec)

##缩小join_buffer_size大小后,可以发现SQL扫描记录数上涨,10100=100+5*200
# Time: 2020-04-14T16:11:33.191980+08:00
# User@Host: root[root] @ localhost []  Id:    24
# Query_time: 0.005209  Lock_time: 0.000148 Rows_sent: 47  Rows_examined: 10100
SET timestamp=1586851893;
select * from sbtest1 r join sbtest2 s on r.k=s.k;

五、Batched Key Access Join(BKA)

1、BKA原理

image

1)伪代码

for each row r in R do
    lookupr in S index
    if found s == r
    then get the tuple <r,s>
    use mrr interface output the <r,s> order by rowid

2)处理流程

1.遍历满足过滤条件的驱动表中所有的记录
2.对于驱动表中每一条记录,通过索引进行关联查询,找到所有满足join条件的记录<r,s>
3.通过mrr结果对以上结果集的rowid进行排序
4.遍历结果集,通过主键回表扫描(顺序读),获取需要的字段信息

2、BKA特点

1)BKA算法是对INLJ算法的一个优化,其区别就是在获取到满足join条件记录的结果集后,通过MRR接口对rowid进行排序,以保证后续的回表操作为顺序读来提高

2)BKA算法算法默认关闭。BKA算法的优化基于MRR,所以若需要打开BKA算法,需要打开MRR并关闭mrr_cost_based。

3、相关参数

mysql> show variables like 'optimizer_switch'\G
batched_key_access=off                       //BKA优化,默认关闭
mysql> set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';   //开启BKA

4、示例

root@mysql57 16:19:  [db2]> set session optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
Query OK, 0 rows affected (0.00 sec)

root@mysql57 16:19:  [db2]> explain select * from sbtest1 r join sbtest2 s on r.k=s.k;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
|  1 | SIMPLE      | r     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  100 |   100.00 | NULL                                               |
|  1 | SIMPLE      | s     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  200 |    10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

root@mysql57 16:20:  [db2]> alter table sbtest2 add index idx_k(k);
Query OK, 0 rows affected (0.05 sec)
Records: 0  Duplicates: 0  Warnings: 0

root@mysql57 16:20:  [db2]> explain select * from sbtest1 r join sbtest2 s on r.k=s.k;
+----+-------------+-------+------------+------+---------------+-------+---------+---------+------+----------+----------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref     | rows | filtered | Extra                                  |
+----+-------------+-------+------------+------+---------------+-------+---------+---------+------+----------+----------------------------------------+
|  1 | SIMPLE      | r     | NULL       | ALL  | NULL          | NULL  | NULL    | NULL    |  100 |   100.00 | NULL                                   |
|  1 | SIMPLE      | s     | NULL       | ref  | idx_k         | idx_k | 4       | db2.r.k |    1 |   100.00 | Using join buffer (Batched Key Access) |
+----+-------------+-------+------------+------+---------------+-------+---------+---------+------+----------+----------------------------------------+
2 rows in set, 1 warning (0.00 sec)


##BKA算法是对INLJ算法的优化,其SQL扫描记录数只是驱动表的记录数?
# Time: 2020-04-14T16:20:49.212383+08:00
# User@Host: root[root] @ localhost []  Id:    24
# Query_time: 0.000680  Lock_time: 0.000145 Rows_sent: 47  Rows_examined: 100
SET timestamp=1586852449;
select * from sbtest1 r join sbtest2 s on r.k=s.k;

六、Hashjoin

Hashjoin从MySQL8.0.18后才开始支持,M一SQL 8.0.18之前只支持NLP相关的算法。Hashjoin主要是针对表关联字段无有效索引可利用时进行的一种优化算法。

image

1、In-Memory Join(CHJ)原理及特点

1)构建阶段,遍历驱动表,以join条件为key,查询需要的列作为value创建hash表。

2)探测阶段,根据驱动表构建出来的hash表,对被驱动表的关联列进行遍历,并计算关联列的hash值判断是否满足join条件,获取到满足条件的所有记录的结果集

3)CHJ主要适用于被驱动表关联字段无索引,切驱动表可一次性读取到join_buffer_size的情况,该情况下只需要对驱动表扫描1次,被驱动表扫描1次。

2、On-Disk Hash Join原理及特点

1)如果驱动表的记录无法一次性存储到join_buffer_size中,构建阶段会首先利用hash算将驱动表进行分区,并产生临时分片写到磁盘上;

2)在探测阶段,对被驱动表使用同样的hash算法进行分区,保证相同的分区中,驱动表和被驱动表满足表关联的等值条件的情况下,其hash算法将其分配到相同的分区中。

3)On-Disk Hash Join是基于CHJ,对join_buffer_size无法满足一次性存储所有的驱动表记录情况下的优化

4)当驱动表记录超过内存时,MySQL通过hash算法对其进行分区处理,若分区后部分分区数据仍然超出join_buffer_size的大小,则MySQL会分批次读取该分区的记录,每次处理完后清理hash表,重复以上操作直到处理完所有分区数据。

5)该算法下驱动表扫描次数为1,被驱动表扫描次数与hash算法分区个数以及每个分区下驱动表记录大小/join_buffer_size个数相关,驱动表数据越大其被驱动表扫描次数越多

3、示例

## MySQL 8.0.19默认开启hashjoin,可以使用explain format=tree查看SQL执行计划
root@mysql57 17:16:  [db2]> explain format=tree select * from sbtest1 r join sbtest2 s on r.k=s.k;
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                               |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Inner hash join (s.k = r.k)  (cost=9661.47 rows=2000)
    -> Table scan on s  (cost=76.21 rows=200)
    -> Hash
        -> Table scan on r  (cost=42.50 rows=100)
 |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)


##可以看到hashjoin算法下,SQL扫描记录数为100+1*200,该算法相对于SNLJ和BNL都有所提高
# Time: 2020-04-14T17:16:34.857120+08:00
# User@Host: root[root] @ localhost []  Id:    17
# Query_time: 0.001235  Lock_time: 0.000206 Rows_sent: 47  Rows_examined: 300
SET timestamp=1586855794;
select * from sbtest1 r join sbtest2 s on r.k=s.k;

文章参考:

姜承尧大佬公众号:https://mp.weixin.qq.com/s?__biz=MjM5MjIxNDA4NA==&mid=205923864&idx=1&sn=63b97a02def11c3e4fceb67d25c79628&scene=21#wechat_redirect
【mysql】关于ICP、MRR、BKA等特性:https://www.cnblogs.com/chenpingzhao/p/6720531.html
MySQL8.0 新特性 Hash Join:https://www.cnblogs.com/cchust/p/11961851.html
相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
6月前
|
SQL 算法 关系型数据库
深入理解MySQL中的Join算法
在数据库处理中,Join操作是最基本且最重要的操作之一,它能将不同的表连接起来,实现对数据集的更深层次分析。
341 8
深入理解MySQL中的Join算法
|
存储 安全 算法
MySQL 数据库支持国密算法
数据库加密,作为杀手锏,是数据库底线防守的秘密武器,通过在数据库存储层进行数据加密处理,达到即使数据被黑客盗取也无法解密的效果,从根源上解决数据泄露问题。 近年,市场对于数据库加密产品的需求呈上升趋势,但由于技术门槛极高,国内真正能够提供此类产品的企业本就寥寥无几,尤其针对全球份额排名第二的MySQL数据库,能够对其支持的加密产品一直没有出现。 不同于传统的视图+触发器模式的透明加密方式,本文所提MySQL国密加密产品采用数据库引擎代码改造技术,真正实现数据在存储层的加、解密功能,避免以往加密过程中,数据库文件导入导出的繁琐方法,最大程度减少性能损失。 产品是为用户需求而生,而我们要做的
1391 0
|
4月前
|
消息中间件 算法 NoSQL
45k以上突击面试必备,redis+mysql+并发+spring+算法+导图等
今天小编给大家带来的一篇关于Java面试相关的电子文档资源,介绍了关于Java、面试题方面的内容,本书是由Java官网出版,格式为DOC,资源大小62.5 MB,目前豆瓣、亚马逊、当当、京东等电子书综合评分为:8.7。
|
6月前
|
算法 Java 关系型数据库
JSP基于DES算法管理系统myeclipse开发mysql数据库web结构java编程jsp展现
JSP 基于DES算法管理系统是一套完善的web设计系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,mysql数据库存储,系统主要采用B/S模式开发。
41 0
|
6月前
|
缓存 算法 关系型数据库
面试官:你知道MySQL和Linux操作系统是如何改进LRU算法的吗?
上周群里看到有位小伙伴面试时,被问到这两个问题: 咋一看,以为是在问操作系统的问题,其实这两个题目都是在问如何改进 LRU 算法。 因为传统的 LRU 算法存在这两个问题: 「预读失效」导致缓存命中率下降(对应第一个问题) 「缓存污染」导致缓存命中率下降(对应第二个问题) Redis 的缓存淘汰算法则是通过实现 LFU 算法来避免「缓存污染」而导致缓存命中率下降的问题(Redis 没有预读机制)。 MySQL 和 Linux 操作系统是通过改进 LRU 算法来避免「预读失效和缓存污染」而导致缓存命中率下降的问题。 这次,就重点讲讲 MySQL 和 Linux 操作系统是如何改进 L
|
8月前
|
SQL 算法 关系型数据库
MySQL中的Join 的算法(NLJ、BNL、BKA)
MySQL中的Join 的算法(NLJ、BNL、BKA)
149 0
|
11月前
|
SQL 自然语言处理 算法
MySQL - Join关联查询优化 --- NLJ及BNL 算法初探
MySQL - Join关联查询优化 --- NLJ及BNL 算法初探
104 0
|
SQL 算法 关系型数据库
MySQL中索引与算法
该文章所用到的表结构
83 0
MySQL中索引与算法
|
SQL 算法 关系型数据库
mysql连接查询底层算法
mysql连接查询底层算法
153 0
|
缓存 运维 负载均衡
真是经典中的经典!MySQL+多线程+Redis+算法+网络
真是经典中的经典!MySQL+多线程+Redis+算法+网络
真是经典中的经典!MySQL+多线程+Redis+算法+网络