【OpenGauss源码学习 —— 执行算子(Append算子)】
执行算子(Append算子)
- Append 算子
- ExecInitAppend 函数
- exec_append_initialize_next 函数
- ExecAppend 函数
- ExecEndAppend 函数
- ExecReScanAppend 函数
声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss1.1.0 的开源代码和《OpenGauss数据库源码解析》一书、《PostgresSQL数据库内核分析》一书以及相关学习资料。
Append 算子
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 函数
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 函数
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 {// 如果存在下一个扫描,则返回 truereturn TRUE;}
}
函数的调式信息如下所示:
(gdb) p whichplan
$1 = 0
(gdb) p appendstate->as_nplans
$2 = 3
因此,whichplan < appendstate->as_nplans 条件成立,返回 TURE。
ExecAppend 函数
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++;elsenode->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 函数
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 函数
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 重新扫描所有子查询,确保计划的执行结果是最新的。
相关文章:
【OpenGauss源码学习 —— 执行算子(Append算子)】
执行算子(Append算子) Append 算子ExecInitAppend 函数exec_append_initialize_next 函数ExecAppend 函数ExecEndAppend 函数ExecReScanAppend 函数 声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和…...

Java(一)(引用类型的参数在传递,方法重载,面向对象编程基础)
基本类型和引用类型的参数在传递的时候有什么不同? 基本类型的值传递:参数传输存储的数据值 引用类型的值传递:参数传输存储的地址值 传递数组名字的时候,传递的是数组的地址,change方法可以通过地址直接访问我们在堆内存中开辟的数组,然后改变数组,数组中的元素发生变化 方…...
Vue第1天:特性概览
文章目录 Vue.js 简介 Vue的特性 如何使用Vue 安装Vue 通过CDN引入 使用npm 创建Vue实例 结语 Vue.js 简介 Vue.js(通常简称为Vue)是一款流行的JavaScript框架,专注于构建用户界面。它的设计灵感来自于现代的JavaScript框架…...
C++语法基础知识面经汇总
背景:汇总了网上C常考的基础知识,方便复习 1,static关键字 static可以用于成员变量,或者成员函数。存储空间在静态存储区(编译器会将其初始化为0,对应的存储空间直到程序执行结束才会释放)&…...
AM@幂级数性质@幂级数和函数求解
文章目录 幂级数性质四则运算性质分析性质求解和函数例例 幂级数性质 和多项式有相似的性质本文介绍用幂级数的性质求解幂级数和函数的两个例子 四则运算性质 若幂级数 ∑ n 0 ∞ a n x n \sum_{n0}^{\infin}a_{n}x^{n} ∑n0∞anxn(1)的收敛半径为 R 1 R_1 R1,和函数为…...
PHP低版本安全问题
目录 1、PHP弱类型问题 1.1 MD5、 SHA1 弱比较问题 1.2 数组 0 1)函数无法处理数组,返回0 2)strcmp 2、特殊字符串导致的问题 2.1 "ffifdyop" 与 md5(string,raw) 2.2 ereg函数漏洞:00 截断 3、正则匹配问…...
结构体——C语言初阶
一.结构体的声明: (1)结构的基础知识: 结构体是一种构造数据类型把不同类型的数据组合成一个整体结构体是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量需要注意的是,结构体是一种…...

基于django电影推荐系统
基于django电影推荐系统 摘要 该Django电影推荐系统是一个简单而基础的框架,旨在展示系统的基本组件。系统包括两个主要模型,即Movie和Rating,用于存储电影信息和用户评分。视图层包括展示电影列表和电影详情的功能,使用模板进行页…...

【问题处理】WPS提示不能启动此对象的源应用程序如何处理?
哈喽,大家好,我是雷工! 最近在用WPS打开word文件中,插入的Excel附件时,无法打开,提示:“不能启动此对象的源应用程序”。 经过上网查找处理办法,尝试解决,现将解决过程记…...

UE 程序化网格 计算横截面
首先在构造函数内加上程序化网格,然后复制网格体到程序化网格组件上,将Static Mesh(类型StaticMeshActor)的静态网格体组件给到程序化网格体上 然后把StaticMesh(类型为StaticMeshActor)Instance暴漏出去 …...

【Spring】IoC容器的一些总结与补充
文章目录 1. 创建容器的两种方式相对路径导入绝对路径导入 2. 获取Bean的三种方式getBean后强转类型getBean内写明类别根据类别获取bean 3. 容器层次结构4. BeanFactory5. bean的总结6. 注入的总结 1. 创建容器的两种方式 相对路径导入 ApplicationContext ctx new ClassPat…...

Java GUI实现五子棋游戏
五子棋是一种双人对弈的棋类游戏,通常在棋盘上进行。棋盘为 1515 的方格,黑白双方各执棋子,轮流在棋盘的格点上落子,先在横、竖、斜线上形成五个相连的同色棋子者获胜。五子棋规则简单,易学难精,兼具攻防和…...

Python 集成 Nacos 配置中心
Python 集成 Nacos 配置中心 下载 Nacos 官方 pyhton 库 pip install nacos-sdk-python # 指定国内阿里云镜像源 pip3 install nacos-sdk-python -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com配置 Nacos 相关信息 Global:nacos:port: 8848…...
Debian 11 更新 Node.js 版本
发布于 2023-07-14 在 https://chenhaotian.top/debian/d-upd-nodejs/ 步骤 从 NodeSource 服务下载需要的 Node.js 安装脚本。注意更换版本号。当前的 LTS 版本是 18.x curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -现在可以直接从 apt 安装࿰…...

python 对图像进行聚类分析
import cv2 import numpy as np from sklearn.cluster import KMeans import time# 中文路径读取 def cv_imread(filePath, cv2_falgcv2.COLOR_BGR2RGB): cv_img cv2.imdecode(np.fromfile(filePath, dtypenp.uint8), cv2_falg) return cv_img# 自定义装饰器计算时间 def…...

程序员导航站
探路者 hello.alluniverse.vip 开发者导航 - Pro Developer网站导航 探路者是一款极简导航工具,致力于收录的每个站点都有其独特的作用。同时支持自定义导航,让用户快速实现个性化的导航站点。 特性概述 免费ChatGPT 装机必备 开发工具 Git精选项目 …...

BIO、NIO、AIO三者的区别及其应用场景(结合生活例子,简单易懂)
再解释三者之前我们需要先了解几个概念: 阻塞、非阻塞:是相较于线程来说的,如果是阻塞则线程无法往下执行,不阻塞,则线程可以继续往下 执行。同步、异步:是相较于IO来说的,同步需要等待IO操作完…...

深度学习YOLO图像视频足球和人体检测 - python opencv 计算机竞赛
文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络4 Yolov5算法5 数据集6 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 深度学习YOLO图像视频足球和人体检测 该项目较为新颖,适合作为竞赛课题方向,学长非…...

系列七、JVM的内存结构【堆(Heap)】
一、概述 一个JVM实例只存在一个堆内存,堆内存的大小是可以手动调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行,堆内存分为三个部分,即…...

什么是Selenium?如何使用Selenium进行自动化测试?
什么是 Selenium? Selenium 是一种开源工具,用于在 Web 浏览器上执行自动化测试(使用任何 Web 浏览器进行 Web 应用程序测试)。 等等,先别激动,让我再次重申一下,Selenium 仅可以测试Web应用…...

基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...