为什么很多人不推荐mysql连表join查询-join连表原理的几种算法

为什么很多人不推荐mysql连表join查询

join查询是什么?是连表查询,我们需要两个表的数据,就会使用join来进行连表。那么mysql里面是怎么连表的呢?它和我们自己查询出一张表的数据在遍历去查询另外一个表是不是一样呢?

join查询

join查询还可以写成left join,表示的是根据左边的表来查询右边的表。但实际上,优化器会进行优化,选择合适的表来做驱动表,不一定是左边的表。

Index Nested-Loop Join

NLJ算法,这是一种基于索引的算法,如果你的连表用到了索引,则会使用这个算法。

比如下面的查询语句:

b.uid字段有索引,并且假设a是小表,优化器选择了a表作为驱动表,b表作为被驱动表。

select * from a left join b on a.id = b.uid;

执行流程大致如下:

  1. 读取表a的第一行数据
  2. 使用表a的id去b表的索引树上查询到对应的b表id
  3. 通过查询的b表id回表查询b表数据
  4. 把a表和b表数据放在一起
  5. 重复上面的步骤直到a表遍历完成
  6. 返回数据

可以看到这和我们自己查询出一张表然后遍历查询过程是一样的。

但是它的优势是省略了多次连接数据库,连接数据库是比较耗费资源和时间的,这样来看,使用join是更加好的选择。当然前提是你的join被驱动表有索引。

这个过程扫描了整个表a和表a的id对应的每一行表b。假设a表的数据量是n,那么扫描了n + n行,当然不算回表。如果算上回表其实相当于n + n + n行。

我们再看一下这个算法的时间复杂度。

b+树中,定位一个记录的时间复杂度大约是log(m)。m是b表的数据行数。为什么这里使用了b表的数据行数而不是上面说的a表的行数呢,因为这里指的是b表索引树的时间复杂度,当然是跟b表索引树大小挂钩也就是b表数据大小挂钩了。如果在算上回表查询,那么时间复杂度大约是2log(m)。

a表要进行全表扫描,那么a表的时间复杂度就是n,再加上每一行要去b表中查询,那么去b表查询的时间就是n * 2log(m)。加起来就是n + (2nlog(m))。

这里面2是常量可以忽略不记,而且2是因为回表造成的,如果我们使用覆盖索引,那么这个2就可以去掉了。

nlog(m) 比 n小,所以显然n的影响是最大的。也就是说n越小,那么我们连表的速度就越快。所以我们连表的时候要使用更小的那张表作为驱动表,然后给被驱动表的连表字段上面加上索引或者覆盖索引。这样我们的连表其实还是很快的。

Block Nested-Loop Join

如果你的被驱动表字段上面没有索引,那么mysql就会使用另外一种算法。这个算法叫BNL算法。

本来没有索引的情况下,按照上面的流程应该是下面这样:

  1. 读取表a的第一行数据
  2. 使用表a的id去b表上进行全表扫描查询到对应的b表数据
  3. 把a表和b表数据放在一起
  4. 重复上面的步骤直到a表遍历完成
  5. 返回数据

这样的话对于每一行表a的数据都要进行b表的全表扫描,也就是如果表a记为n行,表b记为m行,那么需要扫描n * m + n行数据。和上面的n + (2nlog(m))相比,可以看到慢了多少倍。

所以mysql使用了另外一种方法,也就是BNL算法,这个算法做了一下优化,流程变成了下面这样:

  1. 读取表a的所有数据放入join buffer
  2. 对表b进行全表扫描,然后把每一条数据和join buffer中的数据做对比。
  3. 把满足条件的数据返回。

这样的话,也就是扫描了一遍表a和表b,那么需要扫描n + m行数据。可以看到比上面的n + (nm)来说少了n倍的扫描量。当然了,这个算法,还需要在join buffer中进行nm次对比数据,但是这样内存判断也要比上面的方法好很多。

同样的这个算法对于两张表都是全表扫描,也就无所谓上面说的需要小表做驱动表了,反正都一样,都要全表扫描。

join buffer存在内存中,那么他就有大小的限制,参数join_buffer_size就是限制join buffer大小的。默认值是256k。如果表a的数据大于join buffer的大小,那么就会分段,也就是分多次进行。

但是分多次执行就会有一个问题了。比如分成了c次,那么表b就要经历c次的全表扫描。所以内存允许,当然Join buffer大一点速度会快一些。

这样的话就不是上面的n + m了,而是变成了n + (c*m),c是根据join buffer大小和n的大小来决定的。如果n越小或者join buffer size越大,那么c就越小。也就是说,如果我们使用小表作为驱动表,那么在遇到数据量超过join buffer的时候,速度也会比较快。

总结

总的来说,Join的时候注意下面几点:

  • 总是用小表作为驱动表比较好。
  • 能加索引就在被驱动表join字段上面加索引,使用NLJ算法而不是BNL算法。
  • 在没有索引,并且驱动表数据量过大时,可以通过调大join_buffer_size的值来加速连表查询。
原创文章 84 获赞 7 访问量 5万+

猜你喜欢

转载自blog.csdn.net/Thepatterraining/article/details/105790839