41.6. 执行器

执行器接受规划器/优化器传过来地查询规划然后递归地处理它, 抽取所需要地行集合。它实际上是一个需求-拉动地流水线机制。 每次调用一个规划节点地时候,它都必须给出更多的一个行,或者汇报它已经完成行的传递了。

为了提供一个扎实的例子,假设顶端节点是一个 MergeJoin 节点。 在做任何融合之前,首先得抓取两行(每个子规划一行)。因此执行器递归地调用自己来处理子规划(它从附着在 lefttree上的子规划开始)。 新的顶端节点(左子规划的顶端节点)假设是,一个 Sort 节点, 然后还是需要递归地获取一个输入行。Sort 节点的子节点可能是一个 SeqScan 节点,代表对一个表的实际读取动作。 这个节点的执行导致执行器从表中抓取一行然后把它返回给调用的节点。 Sort 将不断调用它的子节点以获取需要排序的所有行。 在用尽输入之后(由子节点返回一个 NULL 而不是一行表示),Sort 代码执行排序,然后就可以返回它的第一个输出行,也就是按照排序顺序输出的第一行。 它仍然保持剩下的行的排序状态,这样在随后有需求的时候,它就可以按照排序顺序返回这些行。

MergeJoin 节点也会类似地要求从它的右边子规划获取第一行。 然后它比较这两行看看它们是否能连接;如果能,那么它给它的调用者返回一个连接行。 在下一次调用的时候,或者是在它无法连接当前的两行的时候就是这次调用的时候, 它抓取其中一个表的下一行(抓取哪个表取决于比较结果如何),然后再检查看看两个表是否匹配。 最后,其中一个子规划耗尽资源,而 MergeJoin 返回 NULL,表明无法继续生成更多的连接行。

复杂的查询可能包含许多层的规划节点,但是一般的过程都是一样的: 每个节点在每次被调用的时候都计算并返回它的下一个输出行。 每个节点同样负责附加上任何规划器赋予它的选择或者投影表达式。

执行器机制是用于计算所有的四种基本 SQL 查询类型的: SELECTINSERTUPDATE, 和 DELETE。对于 SELECT 而言,顶层的执行器代码只是需要发送查询规划树返回的每一行给客户端。 对于 INSERT,返回的每一行都插入到 INSERT 声明的目标表中。 (一个简单的 INSERT ... VALUES 命令创建一个简单的规划树,包含一个 Result 节点,它只计算得出一个结果行。 但是 INSERT ... SELECT 可能需要执行器的全部能力。) 对于 UPDATE,规划器安排每个计算出来的行都包括所有更新的字段,加上原来的目标行的 TID (元组 ID,或者行 ID); 执行器的顶层使用这些信息创建一个新的更新过的行,并且标记旧行被删除。对于 DELETE, 规划实际上返回的唯一的一个字段是 TID,然后执行器的顶层简单地使用这个 TID 访问每个目标行,并且把它们标记为已删除。