【PostgreSQL内核学习(二十九)—— 执行器(ExecProcNode)】

声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 postgresql-10.1 的开源代码和《OpenGauss数据库源码解析》和《PostgresSQL数据库内核分析》一书

概述

  在文章【PostgreSQL内核学习(二十二)—— 执行器(ExecutePlan)】中详细介绍了 PostgreSQL 数据库中 ExecutePlan 函数的原理和实现细节,强调了它在数据库查询执行机制中的核心作用。文章分析了 ExecutePlan 函数的执行流程,包括初始化变量处理并行执行模式循环执行查询计划节点直到满足退出条件等步骤。此外,还介绍了 ExecProcNode 函数的作用,即执行给定计划节点并返回元组
  本文将深入了解 ExecProcNode原理实现细节ExecProcNode 函数是 ExecutePlan 函数流程中的关键部分,负责实际执行查询计划树中的各个节点并返回结果元组ExecutePlan 通过循环调用 ExecProcNode处理查询计划中的每一个节点,直到获取到空元组,表示没有更多的数据需要处理,或者达到了预定的元组处理数量。简而言之,ExecProcNode 是连接执行器逻辑与具体计划节点执行结果的桥梁。

ExecProcNode 函数

  下面是 ExecProcNode 函数的定义,其作用是执行给定的查询计划节点并返回一个新的元组。如果节点的 chgParam 非空,表示有些参数改变了,这时会先调用 ExecReScan 函数来处理这些变化。然后,通过调用节点自身的 ExecProcNode 方法来执行节点并获取元组。这个过程是 PostgreSQL 执行计划操作的核心,它确保了在查询执行过程中,每个节点根据当前的状态和数据动态地生成结果。函数源码如下所示:(路径:postgresql-10.1\src\include\executor\executor.h

/* ----------------------------------------------------------------
 *		ExecProcNode
 *
 *		Execute the given node to return a(nother) tuple.
 * ----------------------------------------------------------------
 */
#ifndef FRONTEND
static inline TupleTableSlot *
ExecProcNode(PlanState *node) // 执行给定的查询计划节点,返回一个元组。
{
	if (node->chgParam != NULL) /* something changed? */
		ExecReScan(node);		/* 如果节点的参数有变化,重新扫描节点以应对这些变化。*/

	return node->ExecProcNode(node); // 调用节点自身的ExecProcNode方法执行该节点,并返回结果元组。
}
#endif

  在 PostgreSQL 中,node->ExecProcNode 通过函数指针指向具体的执行函数。当一个 PlanState 结构体被初始化时,根据不同的节点类型(如 SeqScanJoin 等),ExecProcNode 指针会被设置为指向相应节点特定的执行函数。这样,当调用 node->ExecProcNode(node); 时,实际上是调用了与节点类型相匹配的具体执行函数。这种设计允许多态行为,即同一接口可以根据不同的节点类型执行不同的操作,从而实现了查询计划的灵活执行。

ExecProcNodeFirst 函数

  ExecProcNodeFirst 函数是 ExecProcNode 的一个封装器,用于在第一次调用具体节点的执行函数之前执行一些一次性的检查。这包括执行栈深度检查,以确保不会因为递归调用或深层嵌套查询而超出系统栈空间。此外,如果需要,它还会将节点的执行函数指针从 ExecProcNode 更改为 ExecProcNodeInstr,用于收集执行过程中的性能数据。如果不需要性能数据收集,它会直接将执行函数指针指向实际的执行函数 ExecProcNodeReal。这样,ExecProcNodeFirst 确保在查询执行过程中,相关的性能检查和调整只在第一次执行时发生,优化了之后的执行效率。函数源码如下所示:(路径:postgresql-10.1\src\backend\executor\execProcnode.c

static TupleTableSlot *ExecProcNodeFirst(PlanState *node)
{
    // 在节点第一次执行前,执行栈深度检查。因为在某些架构上(如x86),这并不是一个低成本的操作。
    // 这个假设是基于ExecProcNode对给定计划节点的调用总是在大致相同的栈深度上进行。
    check_stack_depth();

    // 如果需要收集执行信息(instrumentation),则将执行函数指针改为ExecProcNodeInstr,只做性能收集。
    // 否则,我们可以去掉所有的封装器,并直接从现在开始让ExecProcNode()调用相应的函数。
    if (node->instrument)
        node->ExecProcNode = ExecProcNodeInstr;
    else
        node->ExecProcNode = node->ExecProcNodeReal;

    // 调用当前节点设置的执行函数,返回处理的元组。
    return node->ExecProcNode(node);
}

PlanState 结构体

  其中,PlanState 结构体是 PostgreSQL 查询执行计划中各种节点共享的基础数据结构。在 PostgreSQL 的执行引擎中,查询计划被编译成一个由多种类型的节点组成的树结构,每个节点类型都有自己特定的作用,如处理数据连接、过滤、聚合等操作PlanState结构体作为所有具体节点类型(如 SeqScanStateJoinState 等)的公共基础,包含了执行查询计划时所需的公共状态信息和控制逻辑。结构体源码如下所示:(路径:postgresql-10.1\src\include\nodes\execnodes.h

typedef struct PlanState
{
    NodeTag     type; // 节点类型标识符,用于区分不同的计划节点类型

    Plan        *plan;            /* 与此状态关联的Plan节点 */

    EState      *state;           /* 执行时,各个节点的状态指向整个顶层计划的一个EState实例 */

    ExecProcNodeMtd ExecProcNode; /* 函数指针,用于返回下一个结果元组 */
    ExecProcNodeMtd ExecProcNodeReal; /* 实际的函数,如果ExecProcNode是一个包装函数的话 */

    Instrumentation *instrument;  /* 可选的,为此节点提供运行时统计信息 */
    WorkerInstrumentation *worker_instrument; /* 每个工作器的性能监测数据 */

    /*
     * 所有Plan类型的公共结构数据。这些到子状态树的链接在相关的计划树中也有对应的链接
     * (除了subPlan列表,在计划树中不存在)
     */
    ExprState  *qual;           /* 布尔条件表达式 */
    struct PlanState *lefttree; /* 输入计划树(可能有多个) */
    struct PlanState *righttree;
    List       *initPlan;       /* 初始化的SubPlanState节点(非相关表达式子选择) */
    List       *subPlan;        /* 表达式中的SubPlanState节点 */

    /*
     * 管理参数变化驱动的重新扫描的状态
     */
    Bitmapset  *chgParam;       /* 已变更参数的ID集合 */

    /*
     * 大多数节点类型所需的其他运行时状态
     */
    TupleTableSlot *ps_ResultTupleSlot; /* 结果元组的槽 */
    ExprContext *ps_ExprContext;    /* 节点的表达式评估上下文 */
    ProjectionInfo *ps_ProjInfo;    /* 进行元组投影的信息 */
} PlanState;

  其中,ExecProcNodeMtdPostgreSQL 查询执行框架中的一个关键组成部分。这个类型的函数指针用于指向实现了特定逻辑的函数,这些函数负责从执行计划的某个节点中获取下一个元组(tuple。如果没有更多的元组可供获取,这些函数将返回 NULL 或一个空的 TupleTableSlot。这个定义允许不同类型的执行节点(如顺序扫描连接操作等)实现各自的元组获取逻辑,而这些逻辑可以通过相同的接口被执行器调用。

/* 定义一个函数指针类型ExecProcNodeMtd */
typedef TupleTableSlot *(*ExecProcNodeMtd) (struct PlanState *pstate);
  • TupleTableSlot *(*ExecProcNodeMtd) (struct PlanState *pstate);:这是一个函数指针的定义,它指向的函数接受一个指向 PlanState 结构体的指针作为参数,返回一个指向 TupleTableSlot 的指针。这里的 PlanState 代表查询执行树中的一个节点的状态,而 TupleTableSlot 是用于存储和传递元组的数据结构。
  • struct PlanState *pstate:函数的参数是一个指向 PlanState 结构的指针,表示当前执行节点的状态。PlanState 包含了执行该节点所需要的所有上下文信息,如节点的执行计划子节点的状态当前节点产生的结果等。
  • TupleTableSlot *:函数的返回类型是一个指向 TupleTableSlo t的指针。TupleTableSlotPostgreSQL 中用于表示一个元组(数据库行)的结构,可以看作是查询结果的一个容器。如果函数返回一个非空的 TupleTableSlot,则表示获取到了一个元组;如果返回 NULL 或一个空的 TupleTableSlot,则表示没有更多的元组可供处理。
  • 12
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值