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

WITH提供了一种方式来书写在一个大型查询中使用的辅助语句。这些语句通常被称为公共表表达式或CTE,它们可以被看成是定义只在一个查询中存在的临时表。在WITH子句中的每一个辅助语句可以是一个SELECT、INSERT、UPDATE或DELETE,并且WITH子句本身也可以被附加到一个主语句,主语句也可以是SELECT、INSERT、UPDATE或DELETE
具体可以参考这个:

如果对CTE有所了解,就会知道,CTE一般不会单独存在,而是依附于一个主查询,换言之CTE是作为一个副查询出现的。所以在主查询中就将副查询作为一个子计划Subplan处理。CTE的执行状态树存放到执行器全局状态Estate的es_subplanstates链表中。

typedef struct EState { NodeTag type; ... /* Parameter info: */ ParamListInfo es_param_list_info; /* values of external params */ ParamExecData *es_param_exec_vals; /* values of internal params */ ... List *es_subplanstates; /* List of PlanState for SubPlans */ ... } EState;

并在CteScan中的ctePlanld存储其子计划在该链表中的偏移量,对应于同一个子计划的CteScan的ctePlanld相同。PostgreSQL在实现时,还为每个CTE在一个全局参数链表中分配了一个空间,其偏移量存储在cteParam中,对应同一个CTE的CteScan对应的偏移量也相同。CteScan节点相关数据结构如下所示。

typedef struct CteScan { Scan scan; int ctePlanId; /* ID of init SubPlan for CTE */ int cteParam; /* ID of Param representing CTE output */ } CteScan;

CteScan节点的初始化过程(ExecInitCteScan函数)将首先初始化CteScanState结构,通过ctePlanld在es_subplanstates中找到对应的子计划执行状态树,并存储在CteScanState的cteplanstate字段中。

然后通过cteParam在执行器全局状态Estate的es_param_exec_vals字段中获取参数结构ParamExecData。若ParamExecData中value为NULL,表示没有其他CteScan对此CTE初始化过存储结构,此时会初始化CteScanState的cte_table字段,并将leader和ParamExecData的value賦值为指向当前CteScanState的指针。若ParamExecData中的value不为NULL,则将其值陚值给leader,让其指向第一个CteScan创建的CteScanState,而不为当前的CteScan初始化cte_table。这样对应一个CTE全局只有一个元组缓存结构,所有使用该CTE的CteScan都会共享该缓存。

typedef struct CteScanState { ScanState ss; /* its first field is NodeTag */ int eflags; /* capability flags to pass to tuplestore */ int readptr; /* index of my tuplestore read pointer */ PlanState *cteplanstate; /* PlanState for the CTE query itself */ /* Link to the "leader" CteScanState (possibly this same node) */ struct CteScanState *leader; /* The remaining fields are only valid in the "leader" CteScanState */ Tuplestorestate *cte_table; /* rows already read from the CTE query */ bool eof_cte; /* reached end of CTE query? */ } CteScanState;

最后。在做一些初始化工作,比如初始化处理元组的表达式上下文、子表达式、元组表、结果元组表等等。

在执行CteScan节点时,将首先査看cte_table指向的缓存中是否缓存元组(缓存结构Tuplestorestate),如果有可直接获取,否则需要先执行ctePlanld指向的子计划获取元组。

CteScan节点的清理过程需要清理元组缓存结构,但只需清理leader指向自身的CteScanState。

12.WorkTableScan 节点

这个节点是和RecursiveUnion节点紧密关联的。下面先看例子,一个RecursiveUnion查询:

postgres=# WITH RECURSIVE t(n) AS( postgres(# VALUES(1) postgres(# UNION ALL postgres(# SELECT n+1 FROM t WHERE n<100) postgres-# SELECT sum(n) FROM t; sum ------ 5050 (1 行) 查询计划 QUERY PLAN ------------------------------------------------------------------------- Aggregate (cost=3.65..3.66 rows=1 width=4) CTE t -> Recursive Union (cost=0.00..2.95 rows=31 width=4) -> Result (cost=0.00..0.01 rows=1 width=0) -> WorkTable Scan on t t_1 (cost=0.00..0.23 rows=3 width=4) Filter: (n < 100) -> CTE Scan on t (cost=0.00..0.62 rows=31 width=4) (7 行)

对于递归查询求值,流程如下:

1.计算非递归项。对UNION(但不对UNION ALL),抛弃重复行。把所有剩余的行包括在递归查询的结果中,并且也把它们放在一个临时的工作表中。

2.只要工作表不为空,重复下列步骤:

计算递归项,用当前工作表的内容替换递归自引用。对UNION(不是UNION ALL),抛弃重复行以及那些与之前结果行重复的行。将剩下的所有行包括在递归查询的结果中,并且也把它们放在一个临时的中间表中。

用中间表的内容替换工作表的内容,然后清空中间表。

详细可以看这里:

这里的工作表就是WorkTable。

WorkTableScan会与RecursiveUnion共同完成递归合并子査询。RecursiveUnion会缓存一次递归中的所有元组到RecursiveUnionState结构中,WorkTableScan提供了对此缓存的扫描。

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

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