join连接的底层原理和三种算法实现

773 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情

上篇文章讲到join连接的优化,我们知道了MySQL会有些情况下会将子查询变成inner连接查询,以及inner join和left/right join都有可能会相互转换,都在说能够使用join连接尽量不要使用子查询,这是为什么?本文主要讲述join的三种底层实现,SNLP/INLP/BNLP三种算法的使用场景。

不知道大家有没有注意到前面几篇在讲explain执行计划以及SQL优化的时候发现执行计划的extra字段输出的额外信息如下图:

这里是MySQL5.7,使用的是BNLJ(block nested loop join)一种块嵌套算法 image.png

如果是MySQL8.0版本,会使用hash join一种hash算法,如下图:

image.png

其实上述join优化的使用并不是每个场景都会生效,只有在join连接的情况下,且驱动表的type是all,range,index的时候才会生效,刚开始我一直没有出现这两种算法的使用,我很奇怪,最后被我查到了,之前因为我的表student的字段classId直接关联的是表class的id,使用所以导致驱动表的ref一直是eq_ref所以不会使用join buffer优化,于是我将表classmonitor字段值用class的id值覆盖,然后用monitor字段去充当join的on字段,就可以生效了,如下SQL:

explain select * from student s inner join class c on s.classId = c.monitor;

下面来介绍这几种算法的实现,以及什么时候会使用哪种算法,在正式开始介绍之前我想先说一下这几种算法的概念,嵌套循环算法其实和Java里的嵌套for循环一样,其实我们的join连接不就是相当于遍历然后符合条件的就记录下来吗?我们可以看下伪代码实现,很抽象:

for (int i = 0; i < a_size; i++) {
    for (int j = 0; j < b_size; j++) {
        if (a[i].field = b[j].field){
            //a表的连接字段等于b表的连接字段,符合条件记录下来
        }
    }
}

1.简单嵌套循环算法:SNLJ (simple-nested-loop-join)

当我们的join连接字段没有建立索引或者索引失效,并且数据量较小的情况下,可能会使用此算法优化查询,本质上就是循环匹配,如下图:

image.png 假设a表有100条记录,b表有1000条记录,下面来看下此算法的消耗:

  1. 驱动表的扫描次数:只需要一次。
  2. 被驱动表的扫描次数:100次,因为需要循环100次。
  3. 读取记录数:a+a*b = 100 + 100 * 1000,首先a表自己扫描一次100条,然后每条记录去循环一遍b表。
  4. join比较次数: a * b。
  5. 回表次数:因为没有索引,索引为0。

2.索引嵌套循环算法:INLJ (index-nested-loop-join)

在join的关联字段有索引的情况下,显然对于驱动表使用关联字段去匹配被驱动表可以使用到索引,如下如:

image.png

很明显,此算法的效率比第一种高,因为走了索引,我们都知道索引的数据是排好序的,所以读取磁盘的时候是顺序io,我们来看下此算法的消耗。

  1. 驱动表的扫描次数:只需要一次。
  2. 被驱动表的扫描次数:0次,因为走了索引。
  3. 读取记录数:驱动表条数+匹配次数。
  4. join比较次数: 匹配次数。
  5. 回表次数:如果是主键索引,那么不需要回表,如果是非聚簇索引那么回表次数就是匹配次数。

2.批量嵌套循环算法:INLJ (block-nested-loop-join)

还是在没索引的情况下,此算法是基于优化SNLJ算法的补充,因为在大数据量的情况下,驱动表的每条记录都去循环被驱动表,io的开销太高了,所以此算法就是一次性将多条记录放在一次循环里和被驱动表匹配,如下图:

image.png

  1. 驱动表的扫描次数:只需要一次。
  2. 被驱动表的扫描次数:批量匹配的次数。
  3. 读取记录数:驱动表条数+驱动表条数 * 匹配次数。
  4. join比较次数: 匹配次数。
  5. 回表次数:0次。

补充:一次批量你拿多少条记录和你MySQL的join_buffer和查询字段的大小、多少都有直接关系,所以说尽量只查询需要的字段,非必要字段会占用join_buffer的内存,同时返回的时候还会占用更多的内存和带宽资源如下图:

image.png

工作中我们可以将join_buffer_size设置为服务器最大内存的75%左右。