跟我一起读postgresql源码(十四)——Executor(查询执行模块之——Join节点(下)) (2)

HashJoin是与Hash节点配合使用的,Hashjoin节点的右子节点一定是Hash节点。Hash节点主要完成Hashjoin算法的步骤1以及Hash表的管理。HashJoin节点则负责处理Hash连接算法的其他步骤和功能。

HashJoin的初始化函数ExecInitHashJoin除了做一些基础性的工作外,还要:

1.根据需要构造HashJoinState节点的outer join相关的数据结构hj_NullInnerTupleSlot和hj_NullInnerTupleSlot(例如为LEFT JOIN和ANTI JOIN构造hj_NullInnerTupleSlot以满足对于那些找不到可连接T2元组的T1元组);

2.初始化hash节点、hashstate节点和存放inner tuple的hj_HashTupleSlot;

3.解析HashJoinState中的hashclauses列表,并初始化HashJoinState的其它字段。

值得一提的是inner tuple的hj_HashTupleSlot并不是通过执行ExecProcNode函数从下层节点获取的,HashJoin的inner tuple对应的下层节点是hash节点。hash节点从下层节点一次性整个获取所有的inner tuple,并保存在自有的hashtable中。因此我们通过ExecScanHashBucket函数从该hashtable中获取元组。

hashjoin的执行过程(调用ExecHashJoin)在上面说过了,主要就是一个大循环,在循环里我们:1.构造Hashtable,2.执行HashJoin算法。

下面是hashjoin的执行状态机的状态(前面说到的两个join也用到了状态机模型)。有关状态机的原理学习过编译原理的同学们应该很清楚,这是第一章的内容,小case,洒洒水啦。

/* * States of the ExecHashJoin state machine */ #define HJ_BUILD_HASHTABLE 1 #define HJ_NEED_NEW_OUTER 2 #define HJ_SCAN_BUCKET 3 #define HJ_FILL_OUTER_TUPLE 4 #define HJ_FILL_INNER_TUPLES 5 #define HJ_NEED_NEW_BATCH 6

通过设置状态的处理逻辑和状态间的转移关系,程序在既有的逻辑下按部就班地执行,最后输出我们要的JOINed tuple。
我还是上一张图好了,我也回忆一下过去的学生生涯,画一个编译原理课程上第一章的有限状态机(DFA)模型。

跟我一起读postgresql源码(十四)——Executor(查询执行模块之——Join节点(下))

稍微说明下,其中单箭头是指箭头尾巴的状态在一定条件下会转移到箭头状态,双箭头指的是这两个状态可以互相转化,指向自身的箭头是只自身在做一些处理之后继续回到自身状态,此时内部会再次check是否满足转移条件。粗粗的黑圈指的是终结状态,也就是说在这个状态下程序可以结束并且输出JOINed tuple。而且这个DFA应该也是从一个复杂的NFA化简而来的,所以看起来可能会有一点别扭。

我们可以看到第一步当然是HJ_BUILD_HASHTABLE节点,我们要为inner tuples构造hashtable呢;

有了第一步,即有了inner tuples的hashtable, 这个时候我们自然就需要outer tuples了。所以我们到了HJ_NEED_NEW_OUTER节点。在这个节点我们会去获取outer tuple,如果获取不到,那么我们就要根据情况,或者去HJ_FILL_INNER_TUPLES节点,要么去HJ_NEED_NEW_BATCH节点。反之,如果获取到了outer tuple的话,也并不是就一定万事大吉,万事大吉的话就跳转至HJ_SCAN_BUCKET;不能吃鸡的话,那么自身先做一下处理,再走一波本状态(HJ_NEED_NEW_OUTER);

接下来再说HJ_SCAN_BUCKET节点,到这个节点的话,outer tuple都搞定了,那么我们需要inner tuple(第一步只是构建了hashtable,我们还没从里面取数据),没取到?看来没有匹配的,那麻烦来下一个outer tuple吧,我们要去HJ_FILL_OUTER_TUPLE(为什么不去HJ_NEED_NEW_OUTER?)。取到了的话?满足连接条件么?如果你不是ANTI JOIN的话(ANTI JOIN 丢弃inner 和outer匹配的连接),差不多就可以到此结束返回JOINed tuple了。

然后是HJ_FILL_OUTER_TUPLE节点,到了这里说明inner 和outer没有匹配上,那么我们肯定要去获取下一个outer tuple了,也就是说,要去HJ_NEED_NEW_OUTER,但是我们要知道,如果我们要是可以返回T1 join NULL这种元组的话(满足left join),也是要返回的,这也是我们需要的结果集。也就是说,我们是一定要去HJ_NEED_NEW_OUTER的,但是如果当前情况可以返回的话,还是先返回,下一次再从这里再来就是。

上面说的都是outer,这里就到inner了,这里要说到HJ_FILL_INNER_TUPLES节点了。这里我们要知道如果是右连接或者全连接的话,我们是需要用左边的NULL连接所有的inner的,这里我们就一直获取batch中的inner tuple。直到这个batch用完了,我们要在进入下一个batch,所以最后进入HJ_NEED_NEW_BATCH节点。这个节点不断地获取下一个batch。当我们获取完了。说明这一波刷完了。那么我们进入下一波,又到了HJ_NEED_NEW_OUTER。周而复始。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyzdzs.html