NLJ和BNLJ之JOIN底层算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_40795214/article/details/81838995

Nested Loop Join (NLJ)算法:

NLJ,顾名思义,是指嵌套循环算法,my.oschina.net 上面有一段代码对NLJ做出了说明:

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,            //进行条件匹配,若满足,发给client
      send to client
    }
  }
}

即,将驱动表/外部表的结果集作为循环基础数据,然后循环从该结果集每次一条获取数据作为下一个表的过滤条件查询数据,然后合并结果。如果有多表join,则将前面的表的结果集作为循环数据,取到每行再到联接的下一个表中循环匹配,获取结果集返回给客户端。

 

(此处仅对两层循环分析,多层循环可以将最内层循环看作一层,将其余的看作一层进行分析)

我以为,可以将内层表看作被驱动表,外层表看作驱动表。每次join时,从驱动表中先拿出一条数据和被驱动表进行条件匹配,若匹配成功,则将数据连接后放入结果集。接着,外层的驱动表扫描获取第二条记录,并和被驱动表进行条件匹配,将成功的记录连接后放入结果集,剩余数据以此类推。最后,将结果集发给请求的客户端。

并且,这个模型和C语言的双层循环(或者多层循环)很相似。

for(;;)       //外层循环
{
    for(;;)   //内层循环
    {
        ...
    }
}

Block Nested Loop Join (BNLJ)算法:

 BNLJ,块嵌套循环。BNLJ是优化版的NLJ,BNLJ区别于NLJ的地方是它加入了缓冲区join buffer,它的作用是外层驱动表可以先将一部分数据事先存到join buffer中,然后再和内层的被驱动表进行条件匹配,匹配成功的记录将会连接后存入结果集,等待全部循环结束后,将结果集发给client即完成一次join。

以下是my.oschina.net 上一段对BNLJ的说明:

for each row in t1 matching range {
  for each row in t2 matching reference key {
    store used columns from t1, t2 in join buffer      //将t1,t2的记录放入join buffer
    if buffer is full {                                //如果buffer不为空
      for each row in t3 {                             //t3和t1,t2的combination 进行匹配
        for each t1, t2 combination in join buffer {
          if row satisfies join conditions,
          send to client
        }
      }
      empty buffer                                     //将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
    }
  }
}

 

我认为,BNLJ相对于NLJ的优点在于,驱动层可以先将部分数据加载进buffer,这种方法的直接影响就是将大大减少内层循环的次数,提高join的效率。

举个栗子:

如果内层循环有100条记录,外层循环也有100条记录,这样的话,每次外层循环先将10条记录放到buffer中,内层循环的100条记录每条与这个buffer中的10条记录进行匹配,只需要匹配内层循环总记录数次即可结束一次循环(在这里,即只需要匹配100次即可结束),然后将匹配成功的记录连接后放入结果集中,接着,外层循环继续向buffer中放入10条记录,同理进行匹配,并将成功的记录连接后放入结果集。后续循环以此类推,直到循环结束,将结果集发给client为止。

可以发现,若用NLJ,则需要100 * 100次才可结束,BNLJ则需要100 / block_size * 100 = 10 * 100次就可结束,减少了9/10。

由此,我想到,若是外层驱动表足够小,或者说恰恰是block_size大小的,那么每次的join将会只进行1 *  内层循环总记录数 = 内层循环总记录数 ,即每次只需要循环内层循环总记录数次就可结束并完成循环,即就是小表驱动大表。

 

 

 声明:部分参考自Using join buffer (Block Nested Loop)

展开阅读全文

没有更多推荐了,返回首页