声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss1.1.0 的开源代码和《OpenGauss数据库源码解析》一书、《PostgresSQL数据库内核分析》一书以及相关学习资料。
Append 算子对应的代码源文件是 “nodeAppend.cpp”,用于处理包含一个或多个子计划的链表。Append 算子遍历子计划链表逐个执行子计划,当子计划返回全部结果后,迭代执行下一个子计划。Append 算子通常用于 SQL 中的集合操作中,例如多个 Union All 操作,可以对多个子查询的结果取并集;另外 Append 算子还可以用来实现继承表的查询功能。
以下是对 Append 算子的一些详细描述:
- 多子计划处理:Append 算子通过遍历包含一个或多个子计划的链表,逐个执行这些子计划。每个子计划对应一个子查询,这些子查询的结果将按照它们在链表中的顺序合并输出。
- 顺序合并:子计划的执行是按照它们在链表中的顺序进行的。当一个子计划返回全部结果后,Append 算子迭代执行下一个子计划。这确保了最终输出结果的顺序与子计划在链表中的顺序一致。
- 用途:主要用于处理 SQL 查询中的集合操作,例如多个 UNION ALL 操作。在这种情况下,每个子查询可能对应于一个分支或条件,而 Append 算子将这些分支的结果合并为一个整体结果。
- 继承表查询: Append 算子还常用于实现继承表的查询功能。在面向对象的数据库设计中,可能存在一个包含所有子类的继承表,而每个子类对应一个子查询。Append 算子可以将这些子查询的结果按顺序组合,形成包含所有子类数据的查询结果。
- 执行计划灵活性: Append 算子的存在使得查询优化器能够以更灵活的方式处理复杂的查询结构。通过将多个子计划组合成一个逻辑单元,优化器可以更好地进行成本估算和执行计划选择。
Append算子对应的主要函数如下表所示:
主要函数 | 说 明 |
---|---|
ExecInitAppend | 初始化 Append 节点 |
ExecAppend | 迭代获取元组 |
ExecEndAppend | 关闭 Append 节点 |
ExecReScanAppend | 重新扫描 Append 节点 |
exec_append_initialize_next | 为下一个扫描节点设置状态 |
按照传统,下面我们还是以一个案例来调试一下代码吧,首先执行以下 sql 语句:
-- 创建表
CREATE TABLE employees (
emp_id SERIAL PRIMARY KEY,
emp_name VARCHAR(100),
emp_department VARCHAR(50)
);
-- 插入数据
INSERT INTO employees (emp_name, emp_department) VALUES
('John Doe', 'HR'),
('Alice Smith', 'IT'),
('Bob Johnson', 'Finance');
-- 第一个子查询
SELECT emp_id, emp_name, emp_department FROM employees WHERE emp_department = 'HR'
UNION ALL
-- 第二个子查询
SELECT emp_id, emp_name, emp_department FROM employees WHERE emp_department = 'IT'
UNION ALL
-- 第三个子查询
SELECT emp_id, emp_name, emp_department FROM employees WHERE emp_department = 'Finance';
在这个例子中,我们通过 UNION ALL 连接了三个子查询,每个子查询选择了属于不同部门的雇员。这样的查询可以模拟 Append 算子的效果,将来自不同子查询的结果合并为一个整体结果。请注意,这只是一种模拟,实际的 Append 算子是由数据库系统内部生成的。
ExecInitAppend 函数是为 Append 节点执行计划初始化阶段而设计的。在初始化过程中,它创建了 AppendState 结构体,用于存储 Append 节点的执行状态信息,包括子计划的执行状态数组、当前执行的子计划编号等。接着,它遍历 Append 节点的子计划列表,对每个子计划调用 ExecInitNode 进行初始化,并将结果保存到数组中。此外,还初始化了执行结果存储槽和输出元组类型。最终,函数返回初始化完成的 AppendState 结构体,为执行 Append 节点提供了必要的状态和信息。在执行阶段,该结构体将被用于跟踪和管理 Append 节点的执行过程。函数源码如下所示:(路径:src/gausskernel/runtime/executor/nodeAppend.cpp
)
/* ----------------------------------------------------------------
* ExecInitAppend
*
* 开始执行所有的追加节点子计划。
*
* (这可能是不够高效的,因为追加节点的整个结果可能不会被扫描,
* 但这样所有的结构都会被分配在执行器的顶级内存块中,而不是
* 在对 ExecAppend 的调用的内存块中。)
* ----------------------------------------------------------------
*/
AppendState* ExecInitAppend(Append* node, EState* estate, int eflags)
{
// 创建 AppendState 结构体,用于存储追加节点的执行状态信息
AppendState* appendstate = makeNode(AppendState);
// 子计划状态数组
PlanState** appendplanstates;
// 子计划数量
int nplans;
// 循环变量
int i;
ListCell* lc = NULL;
// 检查是否存在不支持的标志
Assert(!(eflags & EXEC_FLAG_MARK));
/*
* 设置子计划状态的空向量
*/
// 获取子计划的数量
nplans = list_length(node->appendplans);
// 分配存储子计划状态的数组
appendplanstates = (PlanState**)palloc0(nplans * sizeof(PlanState*));
/*
* 为追加节点创建新的 AppendState
*/
// 初始化 AppendState 结构体的基本信息
appendstate->ps.plan = (Plan*)node;
appendstate->ps.state = estate;
appendstate->appendplans = appendplanstates;
appendstate->as_nplans = nplans;
/*
* 杂项初始化
*
* 追加计划没有表达式上下文,因为它们从不调用 ExecQual 或 ExecProject。
*/
// 初始化执行上下文,因为追加节点不涉及 ExecQual 或 ExecProject
/*
* 追加节点仍然具有 Result 存储槽,其中保存了指向元组的指针,因此
* 我们必须对它们进行初始化。
*/
// 初始化 Result 存储槽,用于存储指向元组的指针
ExecInitResultTupleSlot(estate, &appendstate->ps);
/*
* 对要执行的每个子计划调用 ExecInitNode,并将结果保存到数组“appendplans”中。
*/
// 遍历子计划列表,对每个子计划调用 ExecInitNode 进行初始化
i = 0;
foreach (lc, node->appendplans) {
Plan* initNode = (Plan*)lfirst(lc);
// 执行初始化并保存结果到数组中
appendplanstates[i] = ExecInitNode(initNode, estate, eflags);
i++;
}
/*
* 初始化输出元组类型
* 追加的 Result 元组槽始终包含一个虚拟元组,
* 该槽的默认表AM类型为 Heap。
*/
// 初始化输出元组类型
ExecAssignResultTypeFromTL(&appendstate->ps, TAM_HEAP);
appendstate->ps.ps_ProjInfo = NULL;
/*
* 初始化以扫描第一个子计划
*/
// 初始化当前执行的子计划编号
appendstate->as_whichplan = 0;
// 执行初始化下一个子计划的操作
(void)exec_append_initialize_next(appendstate);
// 返回初始化完成的 AppendState 结构体,为执行追加节点提供必要的状态和信息
return appendstate;
}
函数的调用关系如下所示:
#0 ExecInitAppend (node=0x7f15adf87f18, estate=0x7f15ae6aa060, eflags=16) at nodeAppend.cpp:112
#1 0x000000000159910a in ExecInitNodeByType (node=0x7f15adf87f18, estate=0x7f15ae6aa060, eflags=16) at execProcnode.cpp:262
#2 0x0000000001599bf0 in ExecInitNode (node=0x7f15adf87f18, estate=0x7f15ae6aa060, e_flags=16) at execProcnode.cpp:497
#3 0x0000000001607b33 in ExecInitResult (node=0x7f15adf87868, estate=0x7f15ae6aa060, eflags=16) at nodeResult.cpp:239
#4 0x00000000015990d4 in ExecInitNodeByType (node=0x7f15adf87868, estate=0x7f15ae6aa060, eflags=16) at execProcnode.cpp:258
#5 0x0000000001599bf0 in ExecInitNode (node=0x7f15adf87868, estate=0x7f15ae6aa060, e_flags=16) at execProcnode.cpp:497
#6 0x00000000015939ba in InitPlan (queryDesc=0x7f15ae47f860, eflags=16) at execMain.cpp:1437
#7 0x0000000001591404 in standard_ExecutorStart (queryDesc=0x7f15ae47f860, eflags=16) at execMain.cpp:382
#8 0x00007f160055e78a in gs_audit_executor_start_hook (queryDesc=0x7f15ae47f860, eflags=0) at gs_policy_plugin.cpp:1907
#9 0x000000000139a43d in explain_ExecutorStart (queryDesc=0x7f15ae47f860, eflags=0) at auto_explain.cpp:83
#10 0x0000000001590e1b in ExecutorStart (queryDesc=0x7f15ae47f860, eflags=0) at execMain.cpp:228
#11 0x0000000001470c03 in PortalStart (portal=0x7f15ae4ce060, params=0x0, eflags=0, snapshot=0x0) at pquery.cpp:784
#12 0x000000000145d184 in exec_simple_query (
---Type <return> to continue, or q <return> to quit---
函数的调式信息如下所示:
(gdb) p nplans
$1 = 3
(gdb) p *appendstate
$2 = {ps = {type = T_AppendState, plan = 0x7f15adf87f18, state = 0x7f15ae6aa060, instrument = 0x0, targetlist = 0x0, qual = 0x0, lefttree = 0x0,
righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0, hbktScanSlot = {currSlot = 0}, ps_ResultTupleSlot = 0x0, ps_ExprContext = 0x0,
ps_ProjInfo = 0x0, ps_TupFromTlist = false, vectorized = false, nodeContext = 0x0, earlyFreed = false, stubType = 0 '\000', jitted_vectarget = 0x0,
plan_issues = 0x0, recursive_reset = false, qual_is_inited = false, ps_rownum = 0}, appendplans = 0x7f15ae738188, as_nplans = 3, as_whichplan = 0}
(gdb) p *appendstate->ps.plan
$3 = {type = T_Append, plan_node_id = 2, parent_node_id = 1, exec_type = EXEC_ON_DATANODES, startup_cost = 0, total_cost = 38.317499999999995, plan_rows = 3,
multiple = 0, plan_width = 340, dop = 1, pred_rows = -1, pred_startup_time = -1, pred_total_time = -1, pred_max_memory = -1, recursive_union_plan_nodeid = 0,
recursive_union_controller = false, control_plan_nodeid = 0, is_sync_plannode = false, targetlist = 0x7f15adf88140, qual = 0x0, lefttree = 0x0,
righttree = 0x0, ispwj = false, paramno = -1, initPlan = 0x0, distributed_keys = 0x0, exec_nodes = 0x7f15ae56dd40, extParam = 0x0, allParam = 0x0,
vec_output = false, hasUniqueResults = false, isDeltaTable = false, operatorMemKB = {0, 0}, operatorMaxMem = 0, parallel_enabled = false,
hasHashFilter = false, var_list = 0x0, filterIndexList = 0x0, ng_operatorMemKBArray = 0x0, ng_num = 0, innerdistinct = 1, outerdistinct = 1}
(gdb) p *appendstate->ps.state
$4 = {type = T_EState, es_direction = ForwardScanDirection, es_snapshot = 0x7f15ae46c4f8, es_crosscheck_snapshot = 0x0, es_range_table = 0x7f15adf8a6a0,
es_plannedstmt = 0x7f15adf8d728, es_junkFilter = 0x0, es_output_cid = 0, es_result_relations = 0x0, es_num_result_relations = 0,
es_result_relation_info = 0x0, esCurrentPartition = 0x0, esfRelations = 0x0, es_result_remoterel = 0x0, es_result_insert_remoterel = 0x0,
es_result_update_remoterel = 0x0, es_result_delete_remoterel = 0x0, es_trig_target_relations = 0x0, es_trig_tuple_slot = 0x0, es_trig_oldtup_slot = 0x0,
es_trig_newtup_slot = 0x0, es_param_list_info = 0x0, es_param_exec_vals = 0x0, es_query_cxt = 0x7f15ae542b28, es_const_query_cxt = 0x7f15ae543a40,
es_tupleTable = 0x7f15ae5445d0, es_rowMarks = 0x0, es_processed = 0, es_last_processed = 0, es_lastoid = 0, es_top_eflags = 16, es_instrument = 0,
es_finished = false, es_exprcontexts = 0x7f15ae5443f8, es_subplanstates = 0x0, es_auxmodifytables = 0x0, es_remotequerystates = 0x0,
es_per_tuple_exprcontext = 0x0, es_epqTuple = 0x0, es_epqTupleSet = 0x0, es_epqScanDone = 0x0, es_subplan_ids = 0x0, es_skip_early_free = false,
es_skip_early_deinit_consumer = false, es_under_subplan = false, es_material_of_subplan = 0x0, es_recursive_next_iteration = false, dataDestRelIndex = 0,
es_bloom_filter = {bfarray = 0x0, array_size = 0}, es_can_realtime_statistics = false, es_can_history_statistics = false, isRowTriggerShippable = false}
(gdb) p appendstate->as_nplans
$5 = 3
(gdb) p **appendplanstates@2
$14 = {{type = T_SeqScanState, plan = 0x7f15adf885a0, state = 0x7f15ae6aa060, instrument = 0x0, targetlist = 0x7f15ae54c710, qual = 0x7f15ae54d948,
lefttree = 0x0, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0, hbktScanSlot = {currSlot = 0}, ps_ResultTupleSlot = 0x7f15ae54ce88,
ps_ExprContext = 0x7f15ae54c288, ps_ProjInfo = 0x0, ps_TupFromTlist = false, vectorized = false, nodeContext = 0x7f15ae543c68, earlyFreed = false,
stubType = 0 '\000', jitted_vectarget = 0x0, plan_issues = 0x0, recursive_reset = false, qual_is_inited = true, ps_rownum = 0}, {type = 2927208672,
plan = 0x7f15ade6c860, state = 0x7f15ae54cff8, instrument = 0x0, targetlist = 0x0, qual = 0x0, lefttree = 0x0, righttree = 0x0, initPlan = 0x0,
subPlan = 0x0, chgParam = 0x0, hbktScanSlot = {currSlot = 0}, ps_ResultTupleSlot = 0x0, ps_ExprContext = 0x0, ps_ProjInfo = 0x0, ps_TupFromTlist = false,
vectorized = false, nodeContext = 0x0, earlyFreed = false, stubType = 0 '\000', jitted_vectarget = 0x160acad <SeqNext(SeqScanState*)>,
plan_issues = 0x7f15ae543c68, recursive_reset = false, qual_is_inited = 2, ps_rownum = -6510615555426900571}}
exec_append_initialize_next 函数用于设置追加节点状态以准备进行下一个子计划的扫描。该函数检查当前迭代的子计划索引是否在有效范围内,如果是,则返回 true,表示有下一个子计划需要处理;如果超出索引范围,则根据扫描的方向(正向或逆向)进行调整,然后返回 false,通知 ExecAppend 函数已经到达了子计划列表的末尾。这个函数在追加节点执行过程中被调用,用于初始化追加节点状态以准备处理下一个子计划。函数源码如下所示:(路径:src/gausskernel/runtime/executor/nodeAppend.cpp
)
/* ----------------------------------------------------------------
* exec_append_initialize_next
*
* 设置追加状态节点以进行“下一个”扫描。
*
* 如果有“下一个”扫描要处理,则返回 true。
* ----------------------------------------------------------------
*/
bool exec_append_initialize_next(AppendState* appendstate)
{
int whichplan;
/*
* 从追加节点获取信息
*/
whichplan = appendstate->as_whichplan;
if (whichplan < 0) {
/*
* 如果是逆向扫描,我们从列表中的最后一个扫描开始,
* 然后向前进行到第一个.. 在任何情况下,通过返回 FALSE,
* 通知 ExecAppend 我们已经到达了行的末尾
*/
appendstate->as_whichplan = 0;
return FALSE;
} else if (whichplan >= appendstate->as_nplans) {
/*
* 如上,如果超出了列表中的最后一个扫描,结束扫描..
*/
appendstate->as_whichplan = appendstate->as_nplans - 1;
return FALSE;
} else {
// 如果存在下一个扫描,则返回 true
return TRUE;
}
}
函数的调式信息如下所示:
(gdb) p whichplan
$1 = 0
(gdb) p appendstate->as_nplans
$2 = 3
因此,whichplan < appendstate->as_nplans 条件成立,返回 TURE。
ExecAppend 函数负责处理对多个子计划进行迭代的逻辑。它循环遍历子计划,从当前子计划获取元组,如果获取到元组则直接返回;否则,释放当前子计划的资源并切换到下一个子计划,继续循环。在正向或逆向扫描中,如果没有更多的子计划可供处理,则返回一个由 ExecInitAppend 设置的空槽。这样,ExecAppend 实现了对多个子计划的迭代执行,用于支持像多个 Union All 操作这样的集合操作,以及继承表的查询等功能。函数源码如下所示:(路径:src/gausskernel/runtime/executor/nodeAppend.cpp
)
/* ----------------------------------------------------------------
* ExecAppend
*
* 处理多个子计划的迭代。
* ----------------------------------------------------------------
*/
TupleTableSlot* ExecAppend(AppendState* node) {
for (;;) {
PlanState* subnode = NULL; // 当前处理的子计划节点
TupleTableSlot* result = NULL; // 子计划的执行结果
/*
* 确定当前正在处理的子计划
*/
subnode = node->appendplans[node->as_whichplan];
/*
* 从子计划获取一个元组
*/
result = ExecProcNode(subnode);
if (!TupIsNull(result)) {
/*
* 如果子计划返回了结果,则直接返回。我们不使用在
* ExecInitAppend 中设置的结果槽;没有必要使用它。
*/
return result;
}
/* 提前释放每个子计划的资源 */
ExecEarlyFree(subnode);
/*
* 转到适当方向上的“下一个”子计划。如果没有更多的子计划,则返回
* 由 ExecInitAppend 为我们设置的空槽。
*/
if (ScanDirectionIsForward(node->ps.state->es_direction))
node->as_whichplan++;
else
node->as_whichplan--;
if (!exec_append_initialize_next(node))
return ExecClearTuple(node->ps.ps_ResultTupleSlot);
/* 否则回到循环顶部,尝试从新的子计划获取元组 */
}
}
函数的调用关系如下所示:
#0 ExecAppend (node=0x7f15ae738060) at nodeAppend.cpp:185
#1 0x000000000159a253 in ExecProcNodeByType (node=0x7f15ae738060) at execProcnode.cpp:609
#2 0x000000000159a8dd in ExecProcNode (node=0x7f15ae738060) at execProcnode.cpp:769
#3 0x0000000001607807 in ExecResult (node=0x7f15ae544060) at nodeResult.cpp:124
#4 0x000000000159a231 in ExecProcNodeByType (node=0x7f15ae544060) at execProcnode.cpp:604
#5 0x000000000159a8dd in ExecProcNode (node=0x7f15ae544060) at execProcnode.cpp:769
#6 0x0000000001595232 in ExecutePlan (estate=0x7f15ae6aa060, planstate=0x7f15ae544060, operation=CMD_SELECT, sendTuples=true, numberTuples=0,
direction=ForwardScanDirection, dest=0x7f15adf30318) at execMain.cpp:2124
#7 0x0000000001591d6a in standard_ExecutorRun (queryDesc=0x7f15ae47f860, direction=ForwardScanDirection, count=0) at execMain.cpp:608
#8 0x000000000139a5d4 in explain_ExecutorRun (queryDesc=0x7f15ae47f860, direction=ForwardScanDirection, count=0) at auto_explain.cpp:116
#9 0x000000000159188f in ExecutorRun (queryDesc=0x7f15ae47f860, direction=ForwardScanDirection, count=0) at execMain.cpp:484
#10 0x000000000147298f in PortalRunSelect (portal=0x7f15ae4ce060, forward=true, count=0, dest=0x7f15adf30318) at pquery.cpp:1396
#11 0x0000000001471b5c in PortalRun (portal=0x7f15ae4ce060, count=9223372036854775807, isTopLevel=true, dest=0x7f15adf30318, altdest=0x7f15adf30318,
---Type <return> to continue, or q <return> to quit---
函数的调式信息如下所示:
(gdb) p *subnode
$1 = {type = T_SeqScanState, plan = 0x7f15adf885a0, state = 0x7f15ae6aa060, instrument = 0x0, targetlist = 0x7f15ae54c710, qual = 0x7f15ae54d948,
lefttree = 0x0, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0, hbktScanSlot = {currSlot = 0}, ps_ResultTupleSlot = 0x7f15ae54ce88,
ps_ExprContext = 0x7f15ae54c288, ps_ProjInfo = 0x0, ps_TupFromTlist = false, vectorized = false, nodeContext = 0x7f15ae543c68, earlyFreed = false,
stubType = 0 '\000', jitted_vectarget = 0x0, plan_issues = 0x0, recursive_reset = false, qual_is_inited = true, ps_rownum = 0}
(gdb) p result
$2 = (TupleTableSlot *) 0x7f15ae54cff8
(gdb) p *result
$3 = {type = T_TupleTableSlot, tts_isempty = false, tts_shouldFree = false, tts_shouldFreeMin = false, tts_slow = true, tts_tuple = 0x7f15ade6cb28,
tts_dataRow = 0x0, tts_dataLen = -1, tts_shouldFreeRow = false, tts_attinmeta = 0x0, tts_xcnodeoid = 0, tts_per_tuple_mcxt = 0x7f15ae6a0f78,
tts_tupleDescriptor = 0x7f15ae793348, tts_mcxt = 0x7f15ae543c68, tts_buffer = 151, tts_nvalid = 3, tts_values = 0x7f15ae54d9b0, tts_isnull = 0x7f15ae54da18,
tts_mintuple = 0x0, tts_minhdr = {tupTableType = 0 '\000', t_bucketId = 0, t_len = 0, t_self = {ip_blkid = {bi_hi = 0, bi_lo = 0}, ip_posid = 0},
t_tableOid = 0, t_xid_base = 0, t_multi_base = 0, t_xc_node_id = 0, t_data = 0x0}, tts_off = 16, tts_meta_off = 0, tts_tupslotTableAm = TAM_HEAP}
(gdb) p *result.tts_values
$4 = 1
(gdb) p *result.tts_isnull
$5 = false
ExecEndAppend 函数用于关闭 Append 节点的子扫描。它首先获取 AppendState 结构中的子计划数组和计划数量信息,然后遍历关闭每个子计划,通过调用 ExecEndNode 函数完成关闭操作。这样,ExecEndAppend 负责释放 Append 节点相关的资源,确保在执行结束时进行清理。函数源码如下所示:(路径:src/gausskernel/runtime/executor/nodeAppend.cpp
)
/* ----------------------------------------------------------------
* ExecEndAppend
*
* Shuts down the subscans of the append node.
*
* Returns nothing of interest.
* ----------------------------------------------------------------
*/
void ExecEndAppend(AppendState* node)
{
// 获取子计划数组和计划数量信息
PlanState** appendplans = node->appendplans;
int nplans = node->as_nplans;
int i;
/*
* shut down each of the subscans
* 逐个关闭每个子计划
*/
for (i = 0; i < nplans; i++)
ExecEndNode(appendplans[i]); // 调用ExecEndNode函数完成关闭操作
}
函数的调用关系如下所示:
#0 ExecEndAppend (node=0x7f15ae738060) at nodeAppend.cpp:242
#1 0x000000000159c01e in ExecEndNodeByType (node=0x7f15ae738060) at execProcnode.cpp:1084
#2 0x000000000159c5d6 in ExecEndNode (node=0x7f15ae738060) at execProcnode.cpp:1374
#3 0x0000000001607bd4 in ExecEndResult (node=0x7f15ae544060) at nodeResult.cpp:279
#4 0x000000000159bffc in ExecEndNodeByType (node=0x7f15ae544060) at execProcnode.cpp:1075
#5 0x000000000159c5d6 in ExecEndNode (node=0x7f15ae544060) at execProcnode.cpp:1374
#6 0x0000000001594cfa in ExecEndPlan (planstate=0x7f15ae544060, estate=0x7f15ae6aa060) at execMain.cpp:1910
#7 0x0000000001592232 in standard_ExecutorEnd (queryDesc=0x7f15ae47f860) at execMain.cpp:766
#8 0x00000000014954f2 in pgaudit_ExecutorEnd (queryDesc=0x7f15ae47f860) at auditfuncs.cpp:1600
#9 0x00000000014d98be in hypo_executorEnd_hook (queryDesc=0x7f15ae47f860) at hypopg_index.cpp:216
#10 0x00000000015920bb in ExecutorEnd (queryDesc=0x7f15ae47f860) at execMain.cpp:714
#11 0x00000000012bc705 in PortalCleanup (portal=0x7f15ae4ce060) at portalcmds.cpp:280
#12 0x0000000000e68985 in PortalDrop (portal=0x7f15ae4ce060, isTopCommit=false) at portalmem.cpp:498
---Type <return> to continue, or q <return> to quit---
函数的调式信息如下所示:
(gdb) p **appendplans
$1 = {type = T_SeqScanState, plan = 0x7f15adf885a0, state = 0x7f15ae6aa060, instrument = 0x0, targetlist = 0x7f15ae54c710, qual = 0x7f15ae54d948,
lefttree = 0x0, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0, hbktScanSlot = {currSlot = 0}, ps_ResultTupleSlot = 0x7f15ae54ce88,
ps_ExprContext = 0x7f15ae54c288, ps_ProjInfo = 0x0, ps_TupFromTlist = false, vectorized = false, nodeContext = 0x7f15ae543c68, earlyFreed = true,
stubType = 0 '\000', jitted_vectarget = 0x0, plan_issues = 0x0, recursive_reset = false, qual_is_inited = true, ps_rownum = 2}
执行 ExecEndNode 函数后结果如下:
(gdb) p **appendplans
$33 = {type = T_SeqScanState, plan = 0x7f15adf885a0, state = 0x7f15ae6aa060, instrument = 0x0, targetlist = 0x7f15ae54c710, qual = 0x7f15ae54d948,
lefttree = 0x0, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0, hbktScanSlot = {currSlot = 0}, ps_ResultTupleSlot = 0x7f15ae54ce88,
ps_ExprContext = 0x0, ps_ProjInfo = 0x0, ps_TupFromTlist = false, vectorized = false, nodeContext = 0x7f15ae543c68, earlyFreed = true, stubType = 0 '\000',
jitted_vectarget = 0x0, plan_issues = 0x0, recursive_reset = false, qual_is_inited = true, ps_rownum = 2}
ExecReScanAppend 函数用于重新启动 Append 节点的扫描过程。它遍历 Append 节点的所有子计划,对每个子计划执行重新扫描操作。在重新扫描前,函数检查是否有参数变更,如果有,则通过 UpdateChangedParamSet 函数进行变更参数的信号处理。如果子计划的 chgParam 不为 null,表示需要在后续的 ExecProcNode 中重新扫描该子计划。最后,函数重置 Append 节点的当前子计划索引,并调用exec_append_initialize_next 函数重新初始化 Append 节点的状态,以准备开始新的扫描过程。函数源码如下所示:(路径:src/gausskernel/runtime/executor/nodeAppend.cpp
)
void ExecReScanAppend(AppendState* node)
{
int i;
for (i = 0; i < node->as_nplans; i++) {
PlanState* subnode = node->appendplans[i];
/*
* ExecReScan doesn't know about my subplans, so I have to do
* changed-parameter signaling myself.
* ExecReScan不了解我的子计划,因此我必须自己进行参数变更信号处理。
*/
if (node->ps.chgParam != NULL)
UpdateChangedParamSet(subnode, node->ps.chgParam);
/*
* If chgParam of subnode is not null then plan will be re-scanned by
* first ExecProcNode.
* 如果subnode的chgParam不为null,则计划将在第一个ExecProcNode中重新扫描。
*/
if (subnode->chgParam == NULL)
ExecReScan(subnode); // 调用ExecReScan函数重新扫描子计划
}
node->as_whichplan = 0;
(void)exec_append_initialize_next(node); // 重新初始化Append节点的状态
}
ExecReScanAppend 函数通常在执行计划需要重新扫描的时候被调用。这可能是由于外部参数的变化,需要重新执行整个计划。例如,考虑以下情况:
假设有一个包含多个子查询的 Append 节点,每个子查询对应于不同的分区。如果在执行计划期间,某个影响所有子查询结果的外部参数发生变化,为了保证计划的正确性,就需要调用 ExecReScanAppend。例如:
-- 创建一个包含多个子查询的Append计划
EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM table1 WHERE column = 1
UNION ALL
SELECT * FROM table2 WHERE column = 2
UNION ALL
SELECT * FROM table3 WHERE column = 3;
假设在上述计划执行过程中,外部条件 column 的值发生变化,为了反映这个变化,就需要调用 ExecReScanAppend 重新扫描所有子查询,确保计划的执行结果是最新的。