【PostgreSQL内核学习(二十三)—— 执行器(ExecEndPlan)】

发布时间:2024年01月16日

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

概述

??在这三篇文章中,首先是【OpenGauss源码学习 —— 执行器(execMain)】,它详细探讨了 OpenGauss 数据库管理系统中执行器模块的主要功能和实现机制。这部分内容涉及到如何在 OpenGauss 环境下处理和执行 SQL 语句,包括查询计划生成和优化,以及如何有效地管理数据流和处理结果
??接着,【PostgreSQL内核学习(二十一)—— 执行器(InitPlan)】专注于 PostgreSQL 数据库的执行器模块,特别是初始化计划(InitPlan)的部分。这篇文章讨论了在执行查询之前,如何准备和设置执行环境,包括加载必要的数据结构、分配资源等。这是理解 PostgreSQL 查询处理的关键部分,涉及到数据库内核的深层次工作机制。
??最后,【PostgreSQL内核学习(二十二)—— 执行器(ExecutePlan)】则是关于 PostgreSQL 执行器中执行计划ExecutePlan)的详细分析。这部分内容深入探讨了如何将初始化阶段准备好的计划转化为实际的操作,执行具体的查询。它包括了查询的执行流程,以及如何处理和优化各种数据库操作
??简单总结,这三篇文章都集中在数据库管理系统中的执行器模块,讲述了从初始化计划到执行计划的整个过程。通过深入分析 OpenGaussPostgreSQL 中的实现细节,这些文章为理解优化数据库查询处理提供了宝贵的视角。对数据库开发者和研究者来说,这些内容无疑是非常有价值的资源。
??本文最后将深入了解 ExecEndPlan原理实现细节,从而更好地理解数据库查询的执行机制。具体学习的模块如下图红色框体所示:
在这里插入图片描述

ExecEndPlan 函数

??ExecEndPlan 函数的功能是结束查询计划的执行,主要负责清理和释放在查询执行过程中创建和占用的资源。这包括关闭特定节点类型的查询处理处理子计划销毁执行器的元组表(同时释放缓冲区引脚和元组描述符的引用计数),关闭结果关系相关的索引,以及处理触发器状态和行标记。代码的主要目的是确保所有打开的文件和资源得到适当的关闭和释放,以维护系统的稳定性和效率。函数源码如下所示:(路径:src\backend\executor\execMain.c

/* ----------------------------------------------------------------
 *      ExecEndPlan
 *
 *      清理查询计划 —— 关闭文件并释放存储
 *
 * 注意:现在不太需要担心这段代码中的存储释放;
 * FreeExecutorState应该保证释放所有需要释放的内存。我们关心的是
 * 关闭关联关系和丢弃缓冲区引脚。因此,元组表必须被清空或丢弃以确保引脚被释放。
 * ----------------------------------------------------------------
 */
static void
ExecEndPlan(PlanState *planstate, EState *estate)
{
    ResultRelInfo *resultRelInfo;
    int i;
    ListCell *l;

    /*
     * 关闭节点类型特定的查询处理
     */
    ExecEndNode(planstate);

    /*
     * 同样处理子计划
     */
    foreach(l, estate->es_subplanstates)
    {
        PlanState *subplanstate = (PlanState *) lfirst(l);

        ExecEndNode(subplanstate);
    }

    /*
     * 销毁执行器的元组表。实际上我们只关心释放缓冲区引脚和元组描述符的引用计数;
     * 没有必要释放TupleTableSlots,因为包含它们的内存上下文马上就会消失。
     */
    ExecResetTupleTable(estate->es_tupleTable, false);

    /*
     * 如果有的话,关闭结果关系,但在事务提交前保持锁定。
     */
    resultRelInfo = estate->es_result_relations;
    for (i = estate->es_num_result_relations; i > 0; i--)
    {
        /* 先关闭索引,然后关闭关系本身 */
        ExecCloseIndices(resultRelInfo);
        heap_close(resultRelInfo->ri_RelationDesc, NoLock);
        resultRelInfo++;
    }

    /* 关闭根目标关系。 */
    resultRelInfo = estate->es_root_result_relations;
    for (i = estate->es_num_root_result_relations; i > 0; i--)
    {
        heap_close(resultRelInfo->ri_RelationDesc, NoLock);
        resultRelInfo++;
    }

    /* 同样关闭任何触发器目标关系 */
    ExecCleanUpTriggerState(estate);

    /*
     * 关闭任何为[KEY] UPDATE/SHARE所选的关系,同样保持锁定。
     */
    foreach(l, estate->es_rowMarks)
    {
        ExecRowMark *erm = (ExecRowMark *) lfirst(l);

        if (erm->relation)
            heap_close(erm->relation, NoLock);
    }
}

ExecEndNode 函数

??ExecEndNode 函数的主要功能是递归地遍历查询计划中的每个节点,并对每个节点执行特定的结束操作。这包括释放节点相关的资源关闭打开的文件和连接,以及清理节点内部状态。这个过程对于确保在查询执行完毕后,所有的资源都得到妥善处理非常重要,以避免内存泄漏和其他潜在的问题。函数通过对不同类型的节点执行不同的结束操作(如 ExecEndResultExecEndSeqScan 等),实现了对整个查询计划的全面清理。函数源码如下所示:(路径:src\backend\executor\execProcnode.c

/* ----------------------------------------------------------------
 *      ExecEndNode
 *
 *      递归地清理以'node'为根的查询计划中的所有节点。
 *
 *      此操作完成后,查询计划将无法继续处理。这应该在查询计划
 *      完全执行完毕后才调用。
 * ----------------------------------------------------------------
 */
void
ExecEndNode(PlanState *node)
{
    /*
     * 当到达树叶节点时,无需进行任何操作。
     */
    if (node == NULL)
        return;

    /*
     * 确保有足够的栈空间可用。需要在这里检查,除了在ExecProcNode()中检查,
     * 因为不能保证所有节点都能到达ExecProcNode()。
     */
    check_stack_depth();

    if (node->chgParam != NULL)
    {
        bms_free(node->chgParam);
        node->chgParam = NULL;
    }

    switch (nodeTag(node))
    {
        /*
         * 控制节点
         */
        case T_ResultState:
            ExecEndResult((ResultState *) node);
            break;
        // ... 其他控制节点的处理 ...

        /*
         * 扫描节点
         */
        case T_SeqScanState:
            ExecEndSeqScan((SeqScanState *) node);
            break;
        // ... 其他扫描节点的处理 ...

        /*
         * 连接节点
         */
        case T_NestLoopState:
            ExecEndNestLoop((NestLoopState *) node);
            break;
        // ... 其他连接节点的处理 ...

        /*
         * 物化节点
         */
        case T_MaterialState:
            ExecEndMaterial((MaterialState *) node);
            break;
        // ... 其他物化节点的处理 ...

        default:
            elog(ERROR, "无法识别的节点类型: %d", (int) nodeTag(node));
            break;
    }
}

总结

??以上内容描述了 PostgreSQL 数据库中的 ExecEndNode 函数,这是一个用于递归清理查询计划中所有节点的重要功能。该函数在查询计划执行完毕后被调用,负责释放各个节点占用的资源关闭相关文件和连接、以及清理内部状态。它通过检查每个节点的类型,并对不同类型的节点调用相应的结束处理函数(如 ExecEndResultExecEndSeqScan 等),来确保资源得到妥善管理。这个过程对于维护数据库系统的稳定性和效率至关重要,能有效防止内存泄露和其他潜在问题。

文章来源:https://blog.csdn.net/qq_43899283/article/details/135616807
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。