0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

介绍3种实现Nested-Loop Join算法的方法

冬至子 来源:后端元宇宙 作者:binron 2022-11-17 11:56 次阅读

在MySQL的实现中,Nested-Loop Join有3种实现的算法

  • Simple Nested-Loop Join:简单嵌套循环连接
  • Block Nested-Loop Join:缓存块嵌套循环连接
  • Index Nested-Loop Join:索引嵌套循环连接

一、原理篇

1、Simple Nested-Loop Join

比如:

SELECT *
FROM user u
 LEFT JOIN class c ON u.id = c.user_id

我们来看一下当进行 join 操作时,mysql是如何工作的:

图片

当我们进行left join连接操作时,左边的表是 「驱动表」 ,右边的表是**「被驱动表」**

特点

Simple Nested-Loop Join 简单粗暴容易理解,就是通过双层循环比较数据来获得结果,但是这种算法显然太过于粗鲁,如果每个表有1万条数据,那么对数据比较的次数=1万 * 1万 =1亿次,很显然这种查询效率会非常慢。

「这个全是磁盘扫描!」

❝因为每次从驱动表取数据比较耗时,所以MySQL即使在没有索引命中的情况下也并没有采用这种算法来进行连接操作,而是下面这种!

2、Block Nested-Loop Join

同样以上面的sql为例,我们看下mysql是如何工作的

SELECT *
FROM user u
 LEFT JOIN class c ON u.id = c.user_id

图片

因为每次从驱动表取一条数据都是磁盘扫描所有比较耗时。

这里就做了优化就是 「每次从驱动表取一批数据放到内存中,然后对这一批数据进行匹配操作」

这批数据匹配完毕,再从驱动表中取一批数据放到内存中,直到驱动表的数据全都匹配完毕。

这块内存在MySQL中有一个专有的名词,叫做 join buffer,我们可以执行如下语句查看 join buffer 的大小

show variables like '%join_buffer%'

图片

另外,Join Buffer缓存的对象是什么,这个问题相当关键和重要。

❝Join Buffer存储的并不是驱动表的整行记录,具体指所有参与查询的列都会保存到Join Buffer,而不是只有Join的列。

比如下面sql

SELECT a.col3
FROM a JOIN b ON a.col1 = b.col2
WHERE a.col2 > 0 AND b.col2 = 0

上述SQL语句的驱动表是a,被驱动表是b,那么存放在Join Buffer中的列是所有参与查询的列,在这里就是(a.col1,a.col2,a.col3)。

也就是说查询的字段越少,Join Buffer可以存的记录也就越多!

变量join_buffer_size的默认值是256K,显然对于稍复杂的SQL是不够用的。好在这个是会话级别的变量,可以在执行前进行扩展。

建议在会话级别进行设置,而不是全局设置,因为很难给一个通用值去衡量。另外,这个内存是会话级别分配的,如果设置不好容易导致因无法分配内存而导致的宕机问题。

-- 调整到1M
set session join_buffer_size = 1024 * 1024 * 1024;
-- 再执行查询
SELECT a.col3
FROM a JOIN b ON a.col1 = b.col2
WHERE a.col2 > 0 AND b.col2 = 0

3、Index Nested-Loop Join

当我们了解**「Block Nested-Loop Join」** 算法,我们发现虽然可以将驱动表的数据放入 「Join Buffer」 中,但是缓存中的每条记录都要和被驱动表的所有记录都匹配一遍,也会非常耗时,所以我们应该如何提高被驱动表匹配的效率呢?

其实很简单 就是给被驱动表连接的列加上索引,这样匹配的过程就非常快,如图所示

图片

上面图中就是先匹配索引看有没有命中的数据,有命中数据再回表查询这条记录,获取其它所需要的数据,但列的数据在索引中都能获取那都不需要回表查询,效率更高!

二、SQL示例

1、新增表和填充数据

-- 表1 a字段加索引 b字段没加
CREATE TABLE `t1` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `a` int DEFAULT NULL COMMENT '字段a',
  `b` int DEFAULT NULL COMMENT '字段b',
  PRIMARY KEY (`id`),
  KEY `idx_a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 表2
 create table t2 like t1;
-- t1插入10000条数据 t2插入100条数据
 drop procedure if exists insert_data;
 delimiter ;;
 create procedure insert_data()
 begin
 declare i int;
 set i = 1;
 while ( i <= 10000 ) do
 insert into t1(a,b) values(i,i);
  set i = i + 1;
 end while;
 set i = 1;
 while ( i <= 100) do
 insert into t2(a,b) values(i,i);
  set i = i + 1;
 end while;
 end;;
 delimiter ;
 call insert_data();

2、Block Nested-Loop Join算法示例

-- b字段没有索引
explain select t2.* from t1 inner join t2 on t1.b= t2.b; 
-- 执行结果
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
|  1 | SIMPLE      | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   100 |   100.00 | NULL                                               |
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 10337 |    10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+

从执行计划我们可以得出一些结论:

  • 驱动表是t2,被驱动表是t1。所以使用 inner join 时,排在前面的表并不一定就是驱动表。
  • Extra 中 的 「Using join buffer (Block Nested Loop)」 说明该关联查询使用的是 BNLJ 算法。

上面的sql大致流程是:

  1. 将 t2 的所有数据放入到 join_buffer
  2. 将 join_buffer 中的每一条数据,跟表t1中所有数据进行比较
  3. 返回满足join 条件的数据

3、Index Nested-Loop Join 算法

-- a字段有索引
EXPLAIN select * from t1 inner join t2 on t1.a= t2.a;   
-- 执行结果
+----+-------------+-------+------------+------+---------------+-------+---------+-----------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref             | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+-------+---------+-----------------+------+----------+-------------+
|  1 | SIMPLE      | t2    | NULL       | ALL  | idx_a         | NULL  | NULL    | NULL            |  100 |   100.00 | Using where |
|  1 | SIMPLE      | t1    | NULL       | ref  | idx_a         | idx_a | 5       | mall_order.t2.a |    1 |   100.00 | NULL        |
+----+-------------+-------+------------+------+---------------+-------+---------+-----------------+------+----------+-------------+

从执行计划我们可以得出一些结论:

  • 我们可以看出 t1的type不在是all而是ref,说明不在是全表扫描,而是走了idx_a的索引。
  • 这里并没有出现 「Using join buffer (Block Nested Loop)」 ,说明走的是Index Nested-Loop Join。

上面的sql大致流程是:

  1. 从表 t2 中读取一行数据
  2. 从第 1 步的数据中,取出关联字段 a,到表 t1 idx_a 索引中查找;
  3. 从idx_a 索引上找到满足条件的数据,如果查询数据在索引树都能找到,那就可以直接返回,否则回表查询剩余字段属性再返回。
  4. 返回满足join 条件的数据

我们发现这里效率最大的提升在于,t1表中rows=1,也就是说因为idx_a 索引的存在,不需要把t1每条数据都遍历一遍,而是通过索引1次扫描可以认为最终只扫描 t1 表一行完整数据。

三、join优化总结

根据上面的知识点我们可以总结以下有关join优化经验:

  1. 在关联查询的时候,尽量在被驱动表的关联字段上加索引,让MySQL做join操作时尽量选择INLJ算法

2)小表做驱动表!

当使用left join时,左表是驱动表,右表是被驱动表,当使用right join时,右表是驱动表,左表是被驱动表,当使用join时,mysql会选择数据量比较小的表作为驱动表,大表作为被驱动表,如果说我们在 join的时候 明确知道哪张表是小表的时候,可以用 「straight_join」 写法固定连接驱动方式,省去mysql优化器自己判断的时间。

「对于小表定义的明确」

在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与 join 的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表。

3)在适当的情况下增大 join buffer 的大小,当然这个最好是在会话级别的增大,而不是全局级别

4)不要用 * 作为查询列表,只返回需要的列!

这样做的好处可以让在相同大小的join buffer可以存更多的数据,也可以在存在索引的情况下尽可能避免回表查询数据。

审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • MySQL
    +关注

    关注

    1

    文章

    777

    浏览量

    26137
收藏 人收藏

    评论

    相关推荐

    FFT 算法的一 FPGA 实现

    FPGA实现的 FFT 处理器的硬件结构。接收单元采用乒乓RAM 结构, 扩大了数据吞吐量。中间数据缓存单元采用双口RAM , 减少了访问RAM 的时钟消耗。计算单元采用基 2 算法, 流水线结构, 可在
    发表于 11-21 15:55

    DSP的实现方法

    芯片内部用硬件实现,无需进行编程。在上述几种方法中,第1种方法的缺点是速度较慢,一般可用于DSP算法的模拟;第2和第5
    发表于 08-27 13:47

    典型的ADRC算法介绍

    前言  上篇中详细阐述了经典的自抗扰控制算法的原理,本篇将围绕两ADRC算法展开,针对扩张状态观测器的参数整定问题进行详解,同时,对跟踪微分器的几个重要应用进行介绍。两
    发表于 09-07 08:02

    Apriori算法的一优化方法

    介绍关联规则挖掘中的经典算法――Apriori算法的关键思想。针对传统Apriori算法效率上的不足,提出一改进的Apriori
    发表于 04-10 08:48 19次下载

    3DES算法的FPGA高速实现

    摘要:介绍3-DES算法的概要;以Xilinx公司SPARTANII结构的XC2S100为例,阐述用FPGA高速实现3-DES
    发表于 06-20 14:22 1386次阅读
    <b class='flag-5'>3</b>DES<b class='flag-5'>算法</b>的FPGA高速<b class='flag-5'>实现</b>

    Viterbi译码器回溯算法实现

    该文介绍了两Viterbi 译码器回溯译码算法,通过对这两算法硬件实现结构上的优化,给出了这
    发表于 05-28 15:18 33次下载
    Viterbi译码器回溯<b class='flag-5'>算法</b><b class='flag-5'>实现</b>

    3像素历遍方法的比较和实现_Delphi教程

    Delphi教程3像素历遍方法的比较和实现,很好的Delphi的学习资料。
    发表于 03-16 14:55 4次下载

    Join在Spark中是如何组织运行的

    ,我们有必要了解Join在Spark中是如何组织运行的。 SparkSQL总体流程介绍 在阐述Join实现之前,我们首先简单介绍SparkS
    的头像 发表于 09-25 11:35 1999次阅读
    <b class='flag-5'>Join</b>在Spark中是如何组织运行的

    SQL子查询优化是怎么回事

    子查询 (Subquery)的优化一直以来都是 SQL 查询优化中的难点之一。 关联子查询的基本执行方式类似于 Nested-Loop,但是这种执行方式的效率常常低到难以忍受。 当数据量稍大时,必须
    的头像 发表于 02-01 13:55 1901次阅读
    SQL子查询优化是怎么回事

    介绍3种方法跨时钟域处理方法

    介绍3跨时钟域处理的方法,这3种方法可以说是FPGA界最常用也最实用的
    的头像 发表于 09-18 11:33 2.2w次阅读
    <b class='flag-5'>介绍</b><b class='flag-5'>3</b><b class='flag-5'>种方法</b>跨时钟域处理<b class='flag-5'>方法</b>

    LOOP指令——汇编语言学习笔记3

    实现乘法的例子四、总结LOOP功能与格式功能:实现循环(计数型循环)指令格式:LOOP 标号一、LOOP指令实例以下是一个
    发表于 01-18 08:30 4次下载
    <b class='flag-5'>LOOP</b>指令——汇编语言学习笔记<b class='flag-5'>3</b>

    如何优化MySQL中的join语句

    在mysql中,join 主要有Nested Loop、Hash Join、Merge Join 这三
    的头像 发表于 04-24 17:03 634次阅读
    如何优化MySQL中的<b class='flag-5'>join</b>语句

    一文终结SQL子查询优化

    子查询(Subquery)的优化一直以来都是 SQL 查询优化中的难点之一。关联子查询的基本执行方式类似于 Nested-Loop,但是这种执行方式的效率常常低到难以忍受。
    的头像 发表于 04-28 14:19 556次阅读
    一文终结SQL子查询优化

    大模型+多模态的3实现方法

    我们知道,预训练LLM已经取得了诸多惊人的成就, 然而其明显的劣势是不支持其他模态(包括图像、语音、视频模态)的输入和输出,那么如何在预训练LLM的基础上引入跨模态的信息,让其变得更强大、更通用呢?本节将介绍“大模型+多模态”的3
    的头像 发表于 12-13 13:55 1069次阅读
    大模型+多模态的<b class='flag-5'>3</b><b class='flag-5'>种</b><b class='flag-5'>实现</b><b class='flag-5'>方法</b>

    arduino如何停止loop循环

    退出这个循环。本文将详细介绍如何在Arduino中停止loop循环。 在Arduino中,可以通过使用一个布尔变量或条件语句来实现停止loop循环的功能。下面我们将逐步讨论这些
    的头像 发表于 02-14 16:24 2212次阅读