【PostgreSQL内核学习(十二)—— (PortalStart)】

PortalStart

  • 概述
  • PortalStart 函数
    • ChoosePortalStrategy 函数
    • CreateQueryDesc 函数
    • PushActiveSnapshot 函数
    • ExecutorStart 函数
    • PortalGetPrimaryStmt 函数
    • ExecTypeFromTLInternal 函数
    • UtilityTupleDescripto 函数

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

概述

  在【PostgreSQL内核学习(八)—— 查询执行(查询执行策略)】中我们了解到,Portal 的执行过程为:CreatePortal —> PortalDefineQuery —> PortalStart —> PortalRun —> PortalDrop 。而在【PostgreSQL内核学习(十一)—— (CreatePortal)】一文中,我们介绍了 CreatePortal 函数的执行过程。其中,PortalDefineQuery 函数用于为一个 Portal(查询计划的执行状态)定义查询,包括查询的源文本、命令标签、语句列表和缓存计划等属性。函数比较简单,这里不再做赘述。本文着重来继续学习 PortalStart 函数。

PortalStart 函数

  PortalStart 函数的作用意义启动一个 portal查询计划的执行状态,使其准备好执行查询。以下是该函数的主要作用和意义:

  1. 启动 PortalPortalStart 函数用于启动一个处于初始状态的 portal,将其状态从 PORTAL_DEFINED 更改为 PORTAL_READY,表示该 portal 已准备好执行查询。
  2. 资源分配在启动 portal 之前,它可能需要进行一些资源的分配和初始化,例如为查询结果集分配内存空间、打开游标等。PortalStart 可以在这些资源准备完毕后将 portal 设置为 PORTAL_READY,以便执行查询。
  3. 查询执行一旦 portal 被启动,可以调用 PortalRun 函数来执行查询。PortalStart 通常在执行查询之前调用,确保 portal 处于正确的状态。
  4. 查询计划的执行如果 portal 中关联了查询计划(通过 CachedPlan),PortalStart 可能会执行查询计划,以准备执行查询。这包括对计划的解析重写优化执行等步骤。
  5. 状态转换PortalStart 的一个关键作用是portal 的状态从定义状态更改为准备状态,这是执行查询的先决条件。状态的正确管理对于查询的执行非常重要,因为不同状态下的 portal 具有不同的行为和要求。

  PortalStart 函数的入参含义如下:

  1. portal要启动的 Portal 对象,表示一个查询或命令的执行计划
  2. paramsParamListInfo 类型的参数列表,用于传递参数值给查询或命令。这些参数通常包括绑定的参数值,允许在执行过程中将这些参数的值绑定到查询中。如果不需要参数,可以传入 NULL
  3. eflags整数,表示执行标志Execution Flags),它是一组用于控制查询或命令执行过程的标志位。不同的标志位可以控制查询的行为,例如是否支持回滚、是否允许修改、是否需要获取锁等。具体的标志位取决于执行的需求,通常由上层代码根据情况设置。
  4. snapshotSnapshot 类型,表示当前事务的快照。快照用于控制查询在哪个时间点看到数据库的数据,通常是用于隔离级别的控制。如果不需要使用快照,可以传入 NULL

  PortalStart 函数的源码如下:(路径:src/gausskernel/process/tcop/pquery.cpp

void PortalStart(Portal portal, ParamListInfo params, int eflags, Snapshot snapshot)
{
    // 记录函数进入,用于跟踪
    gstrace_entry(GS_TRC_ID_PortalStart);

    // 保存当前激活的 Portal、资源所有者和 Portal 内存上下文
    Portal saveActivePortal;
    ResourceOwner saveResourceOwner;
    MemoryContext savePortalContext;
    MemoryContext oldContext;
    QueryDesc* queryDesc = NULL;
    int myeflags;
    PlannedStmt* ps = NULL;
    int instrument_option = 0;

    // 断言,确保传入的 Portal 有效
    AssertArg(PortalIsValid(portal));
    // 断言,确保 Portal 的状态为 PORTAL_DEFINED
    AssertState(portal->status == PORTAL_DEFINED);

    /*
     * 设置全局的 portal 上下文指针
     */
    saveActivePortal = ActivePortal;
    saveResourceOwner = t_thrd.utils_cxt.CurrentResourceOwner;
    savePortalContext = t_thrd.mem_cxt.portal_mem_cxt;

    // 开始异常处理块
    PG_TRY();
    {
        // 设置当前激活的 Portal
        ActivePortal = portal;
        // 设置当前的资源所有者为 Portal 的资源所有者
        t_thrd.utils_cxt.CurrentResourceOwner = portal->resowner;
        // 设置当前的 Portal 内存上下文为 Portal 的堆内存上下文
        t_thrd.mem_cxt.portal_mem_cxt = PortalGetHeapMemory(portal);

        // 切换内存上下文到 Portal 的堆内存上下文
        oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));

        // 记录 portal 的参数列表(如果存在)
        portal->portalParams = params;

        /*
         * 确定 portal 的执行策略
         */
        portal->strategy = ChoosePortalStrategy(portal->stmts);

        // 分配并初始化扫描描述符
        portal->scanDesc = (TableScanDesc)palloc0(SizeofHeapScanDescData + MaxHeapTupleSize);

        /*
         * 根据策略启动 portal
         */
        switch (portal->strategy) {
            case PORTAL_ONE_SELECT: {
                ps = (PlannedStmt*)linitial(portal->stmts);

                // 设置快照,除非是只包含 MOT 表的查询
#ifdef ENABLE_MOT
                if (!(portal->cplan != NULL && portal->cplan->storageEngineType == SE_TYPE_MOT)) {
#endif
                    if (snapshot) {
                        PushActiveSnapshot(snapshot);
                    } else {
                        if (u_sess->pgxc_cxt.gc_fdw_snapshot) {
                            PushActiveSnapshot(u_sess->pgxc_cxt.gc_fdw_snapshot);
                        } else {
                            bool force_local_snapshot = false;

                            if (portal->cplan != NULL && portal->cplan->single_shard_stmt) {
                                // 使用单分片时,需要强制使用本地快照
                                force_local_snapshot = true;
                            }
                            PushActiveSnapshot(GetTransactionSnapshot(force_local_snapshot));
                        }
                    }
#ifdef ENABLE_MOT
                }
#endif

                // 确定是否需要执行性能分析(instrumentation)
                if (shouldDoInstrument(portal, ps)) {
                    instrument_option |= INSTRUMENT_TIMER;
                    instrument_option |= INSTRUMENT_BUFFERS;
                }

#ifdef ENABLE_MOT
                Snapshot tempSnap = InvalidSnapshot;
                if (!(portal->cplan != NULL && portal->cplan->storageEngineType == SE_TYPE_MOT)) {
                    tempSnap = GetActiveSnapshot();
                }

                JitExec::JitContext* mot_jit_context =
                    (portal->cplan != NULL) ? portal->cplan->mot_jit_context : nullptr;

                // 在 portal 的上下文中创建 QueryDesc,设置目标为 DestNone
                queryDesc = CreateQueryDesc(
                    ps, portal->sourceText, tempSnap, InvalidSnapshot, None_Receiver, params, 0, mot_jit_context);
#else
                // 在 portal 的上下文中创建 QueryDesc,设置目标为 DestNone
                queryDesc = CreateQueryDesc(
                    ps, portal->sourceText, GetActiveSnapshot(), InvalidSnapshot, None_Receiver, params, 0);
#endif

                // 如果是协调器或单节点模式,同时设置性能分析标志
                if (((IS_PGXC_COORDINATOR && StreamTopConsumerAmI()) || IS_SINGLE_NODE) && ps->instrument_option) {
                    queryDesc->instrument_options |= ps->instrument_option;
                }

                // 检查是否需要跟踪资源
                if (u_sess->attr.attr_resource.use_workload_manager && (IS_PGXC_COORDINATOR || IS_SINGLE_NODE))
                    u_sess->exec_cxt.need_track_resource = WLMNeedTrackResource(queryDesc);

                if (IS_PGXC_COORDINATOR || IS_SINGLE_NODE) {
                    if (u_sess->exec_cxt.need_track_resource) {
                        queryDesc->instrument_options |= instrument_option;
                        queryDesc->plannedstmt->instrument_option = instrument_option;
                    }
                }

                if (!u_sess->instr_cxt.obs_instr &&
                    ((queryDesc->plannedstmt) != NULL && queryDesc->plannedstmt->has_obsrel)) {
                    AutoContextSwitch cxtGuard(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR));

                    u_sess->instr_cxt.obs_instr = New(CurrentMemoryContext) OBSInstrumentation();
                }

                // 如果是可滚动游标,执行器需要支持 REWIND 和倒序扫描
                if (portal->cursorOptions & CURSOR_OPT_SCROLL)
                    myeflags = eflags | EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD;
                // 如果是 WITH HOLD 游标,需要支持 REWIND
                else if (portal->cursorOptions & CURSOR_OPT_HOLD)
                    myeflags = eflags | EXEC_FLAG_REWIND;
                else
                    myeflags = eflags;

                if (ENABLE_WORKLOAD_CONTROL && IS_PGXC_DATANODE) {
                    WLMCreateDNodeInfoOnDN(queryDesc);

                    // 在 DN 上创建 IO 信息
                    WLMCreateIOInfoOnDN();
                }

                // 调用 ExecutorStart 准备执行计划
                ExecutorStart(queryDesc, myeflags);

                // 设置 portal 的查询描述符
                portal->queryDesc = queryDesc;

                // 记录元组描述符(由 ExecutorStart 计算)
                portal->tupDesc = queryDesc->tupDesc;

                // 重置游标位置数据为“查询开始”
                portal->atStart = true;
                portal->atEnd = false; // 允许获取数据
                portal->portalPos = 0;
                portal->posOverflow = false;

#ifdef ENABLE_MOT
                if (!(portal->cplan != NULL && portal->cplan->storageEngineType == SE_TYPE_MOT)) {
#endif
                    PopActiveSnapshot();
#ifdef ENABLE_MOT
                }
#endif
                break;
            }
            case PORTAL_ONE_RETURNING:
            case PORTAL_ONE_MOD_WITH:

                /*
                 * 在运行 portal 之前不启动执行器,但需要设置结果元组描述符。
                 */
                {
                    PlannedStmt* pstmt = NULL;

                    pstmt = (PlannedStmt*)PortalGetPrimaryStmt(portal);
                    AssertEreport(IsA(pstmt, PlannedStmt), MOD_EXECUTOR, "pstmt is not a PlannedStmt");
                    portal->tupDesc = ExecCleanTypeFromTL(pstmt->planTree->targetlist, false, TAM_HEAP);
                }

                // 重置游标位置数据为“查询开始”
                portal->atStart = true;
                portal->atEnd = false; // 允许获取数据
                portal->portalPos = 0;
                portal->posOverflow = false;
                break;

            case PORTAL_UTIL_SELECT:

                /*
                 * 这里不设置快照,因为 PortalRunUtility 函数会负责处理。
                 */
                {
                    Node* ustmt = PortalGetPrimaryStmt(portal);

                    AssertEreport(!IsA(ustmt, PlannedStmt), MOD_EXECUTOR, "ustmt can not be a PlannedStmt");
                    portal->tupDesc = UtilityTupleDescriptor(ustmt);

                    if (portal->tupDesc != NULL)
                    {
                        portal->tupDesc->tdTableAmType = TAM_HEAP;
                    }
                }

                // 重置游标位置数据为“查询开始”
                portal->atStart = true;
                portal->atEnd = false; // 允许获取数据
                portal->portalPos = 0;
                portal->posOverflow = false;
                break;

            case PORTAL_MULTI_QUERY:
                // 目前不需要执行任何操作
                portal->tupDesc = NULL;

                if (ENABLE_WORKLOAD_CONTROL && IS_PGXC_DATANODE) {
                    WLMCreateDNodeInfoOnDN(NULL);

                    // 在 DN 上创建 IO 信息
                    WLMCreateIOInfoOnDN();
                }
                break;
            default:
                break;
        }

        // 记录查询的内存成本
        portal->stmtMemCost = 0;
    }
    // 捕获异常,标记 portal 为失败状态
    PG_CATCH();
    {
        /* Uncaught error while executing portal: mark it dead */
        MarkPortalFailed(portal);

        /* 恢复全局变量并传播错误 */
        ActivePortal = saveActivePortal;
        t_thrd.utils_cxt.CurrentResourceOwner = saveResourceOwner;
        t_thrd.mem_cxt.portal_mem_cxt = savePortalContext;

        PG_RE_THROW();
    }
    PG_END_TRY();

    // 切换回旧的内存上下文
    MemoryContextSwitchTo(oldContext);

    // 恢复全局变量
    ActivePortal = saveActivePortal;
    t_thrd.utils_cxt.CurrentResourceOwner = saveResourceOwner;
    t_thrd.mem_cxt.portal_mem_cxt = savePortalContext;

    // 设置 portal 的状态为 PORTAL_READY,表示已准备好执行查询
    portal->status = PORTAL_READY;

    // 记录函数退出,用于跟踪
    gstrace_exit(GS_TRC_ID_PortalStart);
}

  总结:PortalStart 函数的作用是将一个 portal 从定义阶段切换到准备执行查询的阶段,确保查询所需的资源和状态已经准备好。这是 PostgreSQL 中查询执行过程的关键步骤之一,确保了查询的正确执行。

ChoosePortalStrategy 函数

  ChoosePortalStrategy 函数的主要作用是根据传入的语句列表,选择合适的 Portal 执行策略。根据语句的类型和属性,它会返回不同的执行策略,包括 PORTAL_ONE_SELECTPORTAL_UTIL_SELECTPORTAL_ONE_RETURNINGPORTAL_MULTI_QUERY。选择策略的主要依据是语句的数量类型以及是否包含 RETURNING 子句

其中,执行策略解释如下:

  1. PORTAL_ONE_SELECT单个 SELECT 语句):
  • 意义:该策略适用于仅包含一个 SELECT 查询的情况,而且查询不包含 RETURNING 子句
  • 使用场景:用于执行单个 SELECT 查询,通常不返回任何修改的行,也不需要返回任何结果集的行数。这是一个典型的只读查询。
  1. PORTAL_UTIL_SELECT(实用程序 SELECT)
  • 意义:该策略适用于包含实用程序语句(例如 EXPLAINVACUUM)的情况,这些语句可能返回结果集。
  • 使用场景:用于执行实用程序命令,可能返回结果集,但通常没有 RETURNING 子句。这允许执行查询和获取结果集。
  1. PORTAL_ONE_RETURNING(单个带 RETURNING 的查询)
  • 意义:该策略适用于包含一个带 RETURNING 子句的查询,通常是修改语句(例如 INSERT, UPDATEDELETE)。
  • 使用场景:用于执行只修改一行或一组行的查询,并返回被修改的行。这个策略允许在修改数据的同时获取返回的数据。
  1. PORTAL_MULTI_QUERY(多个查询或命令)
  • 意义:该策略适用于包含多个查询或命令的情况,无法确定具体的执行策略。
  • 使用场景:用于复杂的执行计划,其中可能包含多个查询实用程序命令RETURNING 子句。这种情况需要更通用的执行策略,通常需要执行器根据具体情况来处理。

  ChoosePortalStrategy 函数源码如下:(路径:src/gausskernel/process/tcop/pquery.cpp

/*
 * ChoosePortalStrategy
 * 选择 Portal 执行策略,根据传入的语句列表 stmts。
 *
 * 列表中的元素可以是 Query、PlannedStmt 或实用程序语句。这比 Portal 需要的更通用,
 * 但 plancache.c 也使用了这个函数。
 *
 * 请参考 portal.h 中的注释。
 */
PortalStrategy ChoosePortalStrategy(List* stmts)
{
    int nSetTag;
    ListCell* lc = NULL;

    /*
     * PORTAL_ONE_SELECT 和 PORTAL_UTIL_SELECT 只需要考虑单个语句的情况,因为没有
     * 可以向 SELECT 或实用程序命令添加辅助查询的重写规则。PORTAL_ONE_MOD_WITH 也
     * 只允许一个顶层语句。
     */
    if (list_length(stmts) == 1) {
        Node* stmt = (Node*)linitial(stmts);

        if (IsA(stmt, Query)) {
            Query* query = (Query*)stmt;

            if (query->canSetTag) {
                if (query->commandType == CMD_SELECT && query->utilityStmt == NULL) {
                    if (query->hasModifyingCTE)
                        return PORTAL_ONE_MOD_WITH;
                    else
                        return PORTAL_ONE_SELECT;
                }
                if (query->commandType == CMD_UTILITY && query->utilityStmt != NULL) {
                    if (UtilityReturnsTuples(query->utilityStmt))
                        return PORTAL_UTIL_SELECT;
                    /* 它不能是 ONE_RETURNING,所以放弃 */
                    return PORTAL_MULTI_QUERY;
                }
#ifdef PGXC
                /*
                 * 在 SPI 中使用 EXECUTE DIRECT 时会出现这种情况。
                 * 可能有更好的方法来处理 EXECUTE DIRECT 的情况,比如使用特殊
                 * 的实用程序命令并将其重定向到正确的 Portal 策略。
                 * 类似 PORTAL_UTIL_SELECT 可能更好。
                 */
                if (query->commandType == CMD_SELECT && query->utilityStmt != NULL &&
                    IsA(query->utilityStmt, RemoteQuery)) {
                    return choose_portal_strategy_for_remote_query((RemoteQuery*)query->utilityStmt);
                }
#endif
            }
        }
#ifdef PGXC
        else if (IsA(stmt, RemoteQuery)) {
            return choose_portal_strategy_for_remote_query((RemoteQuery*)stmt);
        }
#endif
        else if (IsA(stmt, PlannedStmt)) {
            PlannedStmt* pstmt = (PlannedStmt*)stmt;

            if (pstmt->canSetTag) {
                if (pstmt->commandType == CMD_SELECT && pstmt->utilityStmt == NULL) {
                    if (pstmt->hasModifyingCTE)
                        return PORTAL_ONE_MOD_WITH;
                    else
                        return PORTAL_ONE_SELECT;
                } else if ((pstmt->utilityStmt != NULL) &&
                           IsA(pstmt->utilityStmt, RemoteQuery)) {
                    return choose_portal_strategy_for_remote_query((RemoteQuery*)pstmt->utilityStmt);
                }
            }
        } else {
            /* 必须是实用程序命令;假设可以设置标签 */
            if (UtilityReturnsTuples(stmt))
                return PORTAL_UTIL_SELECT;
            /* 它不能是 ONE_RETURNING,所以放弃 */
            return PORTAL_MULTI_QUERY;
        }
    }

    /*
     * PORTAL_ONE_RETURNING 必须允许重写添加的辅助查询。
     * 如果有一个可以设置标签的查询,并且它有 RETURNING 列,那么选择 PORTAL_ONE_RETURNING。
     */
    nSetTag = 0;
    foreach (lc, stmts) {
        Node* stmt = (Node*)lfirst(lc);

        if (IsA(stmt, Query)) {
            Query* query = (Query*)stmt;

            if (query->canSetTag) {
                if (++nSetTag > 1)
                    return PORTAL_MULTI_QUERY; /* 无需继续查找 */
                if (query->returningList == NIL)
                    return PORTAL_MULTI_QUERY; /* 无需继续查找 */
            }
        } else if (IsA(stmt, PlannedStmt)) {
            PlannedStmt* pstmt = (PlannedStmt*)stmt;

            if (pstmt->canSetTag) {
                if (++nSetTag > 1)
                    return PORTAL_MULTI_QUERY; /* 无需继续查找 */
                if (!pstmt->hasReturning)
                    return PORTAL_MULTI_QUERY; /* 无需继续查找 */
            }
        }
        /* 否则,实用程序命令,假定无法设置标签 */
    }
    if (nSetTag == 1)
        return PORTAL_ONE_RETURNING;

    /* 否则,属于通用情况... */
    return PORTAL_MULTI_QUERY;
}

  其中 ChoosePortalStrategy 函数的执行过程概括如下:

  1. 首先,函数接受一个参数 stmts,这是一个包含多个语句的列表(可能是查询计划语句实用程序命令)。
  2. 函数开始通过检查 stmts 中的内容来选择合适的 Portal 执行策略
  3. 如果 stmts只包含一个元素,那么函数会检查这个元素的类型,并根据元素的性质和是否包含 RETURNING 子句来选择具体的执行策略。可能的情况包括:
  • 如果是一个单个的 SELECT 查询没有 RETURNING 子句,选择 PORTAL_ONE_SELECT 策略。
  • 如果是一个单个的实用程序命令,且可能返回结果集,选择 PORTAL_UTIL_SELECT 策略。
  • 如果是一个单个的带 RETURNING 子句的查询(通常是修改语句),选择 PORTAL_ONE_RETURNING 策略。
  • 如果不满足以上条件,选择 PORTAL_MULTI_QUERY 作为通用策略
  1. 如果 stmts 中包含多个元素,函数会遍历这些元素计算满足特定条件的元素个数。如果只有一个满足条件的元素,且该元素具有 RETURNING 子句,则选择 PORTAL_ONE_RETURNING 策略。
  2. 如果以上条件都不满足,将选择 PORTAL_MULTI_QUERY 作为通用策略
  3. 最终,函数返回选择的执行策略

  ChoosePortalStrategy函数的主要目的是根据传入的语句列表确定 Portal 的执行策略,以便在执行查询或命令时采取相应的优化和处理方式不同的执行策略适用于不同类型的查询和命令,有助于提高 PostgreSQL 的性能和灵活性

CreateQueryDesc 函数

  CreateQueryDesc 函数的主要作用是创建一个 QueryDesc 结构用于表示一个查询的各种属性和状态信息。这些信息包括操作类型执行计划源文本快照参数目标接收器仪器选项等。在执行查询之前,通常会创建一个 QueryDesc 对象,并将其用于执行器(Executor)的初始化执行过程中。其函数源码如下:(路径:src/gausskernel/process/tcop/pquery.cpp

QueryDesc* CreateQueryDesc(PlannedStmt* plannedstmt, const char* sourceText, Snapshot snapshot,
    Snapshot crosscheck_snapshot, DestReceiver* dest, ParamListInfo params, int instrument_options)
{
    // 分配内存以存储 QueryDesc 结构
    QueryDesc* qd = (QueryDesc*)palloc(sizeof(QueryDesc));

    // 设置操作类型,通常是 SQL 命令的类型(SELECT、INSERT、UPDATE、DELETE 等)
    qd->operation = plannedstmt->commandType;

    // 存储查询的执行计划,即 PlannedStmt 结构
    qd->plannedstmt = plannedstmt;

    // 对于 DECLARE CURSOR,存储其相关的 utilityStmt
    qd->utilitystmt = plannedstmt->utilityStmt;

    // 存储查询的原始文本
    qd->sourceText = sourceText;

    // 注册快照,快照用于查询的隔离级别和一致性
    qd->snapshot = RegisterSnapshot(snapshot);

    // 用于引用完整性检查的快照(可能为空)
    qd->crosscheck_snapshot = RegisterSnapshot(crosscheck_snapshot);

    // 存储查询结果的目标接收器(DestReceiver),用于接收查询结果
    qd->dest = dest;

    // 存储查询中使用的参数值
    qd->params = params;

    // 检查是否是 PGXC 数据节点,并且计划中指定了仪器选项
    if (IS_PGXC_DATANODE && plannedstmt->instrument_option)
        qd->instrument_options = plannedstmt->instrument_option;
    else
        qd->instrument_options = instrument_options; // 是否需要仪器化?

    // 下面的字段在执行器开始时会被设置为合适的值
    qd->tupDesc = NULL;      // 结果元组描述符
    qd->estate = NULL;       // 执行状态(ExecutorState)
    qd->planstate = NULL;    // 计划状态
    qd->totaltime = NULL;    // 总执行时间(Instrumentation)
    qd->executed = false;    // 查询是否已执行

#ifdef ENABLE_MOT
    qd->mot_jit_context = mot_jit_context; // MOT 存储引擎的 JIT 上下文
#endif

    return qd;
}

PushActiveSnapshot 函数

  PushActiveSnapshot 函数用于将传入的快照Snapshot)设置为当前的活动快照。这个函数的作用是将传入的快照设置为当前的活动快照,并维护了活动快照的链表,以便在事务嵌套层次中跟踪和管理活动快照的状态。根据传入的快照类型和属性,它可能会创建副本以确保快照的一致性

注释什么是快照 ?
快照:在数据库管理系统中,快照Snapshot)是一个在某个特定时间点数据库的一致性视图或镜像。它捕捉了数据库在某一瞬间的数据状态,包括表、行、列等数据库对象的内容。快照不仅包含了当前数据的信息,还包含了事务的隔离级别和数据库配置的其他相关信息。

  PushActiveSnapshot 函数源码如下:(路径:src\common\backend\utils\time\snapmgr.cpp

/*
 * PushActiveSnapshot
 *		将传入的快照设置为当前的活动快照
 *
 * 如果传入的快照是静态分配的,或者可能会在将来的命令计数器更新中受到影响,
 * 则创建一个新的长期存在的副本,其活动引用计数为1。否则,仅增加引用计数。
 */
void PushActiveSnapshot(Snapshot snap)
{
    ActiveSnapshotElt* newactive = NULL;

    Assert(snap != InvalidSnapshot);

    // 在顶层事务内存上下文中分配新的 ActiveSnapshotElt 结构
    newactive = (ActiveSnapshotElt*)MemoryContextAlloc(u_sess->top_transaction_mem_cxt, sizeof(ActiveSnapshotElt));

    /*
     * 检查 SecondarySnapshot 在这里可能没有用处,但最好确定一下。
     */
    if (snap == u_sess->utils_cxt.CurrentSnapshot || snap == u_sess->utils_cxt.SecondarySnapshot || !snap->copied ||
        snap == u_sess->pgxc_cxt.gc_fdw_snapshot)
        newactive->as_snap = CopySnapshot(snap);  // 创建传入快照的副本
    else
        newactive->as_snap = snap;

    // 将新的 ActiveSnapshotElt 结构添加到链表中
    newactive->as_next = u_sess->utils_cxt.ActiveSnapshot;
    newactive->as_level = GetCurrentTransactionNestLevel();

    // 增加快照的活动引用计数
    newactive->as_snap->active_count++;

    // 更新当前活动快照链表头指针
    u_sess->utils_cxt.ActiveSnapshot = newactive;
}

ExecutorStart 函数

  ExecutorStart 的函数用于执行查询计划的初始化工作。函数接受两个参数:QueryDesc* queryDescint eflags

  1. queryDesc 参数是之前由 CreateQueryDesc 创建的查询描述对象。该对象包含了有关查询的信息,包括将返回的元组描述以及内部字段estateplanstate)的设置。
  2. eflags 参数包含了一些标志位,这些标志位在 executor.h 中有详细描述。

  函数中还提供了一个钩子函数变量 ExecutorStart_hook,允许可加载插件在调用 ExecutorStart 时获得控制权。这样的插件通常会调用 standard_ExecutorStart 函数。其函数源码如下:(路径:src\gausskernel\runtime\executor\execMain.cpp

/* ----------------------------------------------------------------
 *      ExecutorStart
 *
 *      此例程必须在执行任何查询计划的开始时被调用
 *
 * 接受之前由 CreateQueryDesc 创建的 QueryDesc 对象(之所以分开,是因为某些地方使用 QueryDescs 用于实用程序命令)。
 * QueryDesc 的 tupDesc 字段被填充以描述将要返回的元组,并设置内部字段(estate 和 planstate)。
 *
 * eflags 包含如 executor.h 中描述的标志位。
 *
 * 注意:在调用此例程时的 CurrentMemoryContext 将成为用于此 Executor 调用的查询上下文的父级。
 *
 * 我们提供了一个函数挂钩变量,允许可加载插件在调用 ExecutorStart 时获取控制权。
 * 这样的插件通常会调用 standard_ExecutorStart()。
 * ----------------------------------------------------------------
 */

void ExecutorStart(QueryDesc* queryDesc, int eflags)
{
    gstrace_entry(GS_TRC_ID_ExecutorStart);

    /* 处理插件挂钩可能不安全,因为动态库可能被释放 */
    if (ExecutorStart_hook && !(g_instance.status > NoShutdown))
        (*ExecutorStart_hook)(queryDesc, eflags);
    else
        standard_ExecutorStart(queryDesc, eflags);

    gstrace_exit(GS_TRC_ID_ExecutorStart);
}

  其中,standard_ExecutorStart 函数是 PostgreSQL 执行器的核心部分负责执行查询计划并将结果发送给目标接收器,同时记录性能统计信息。我们会在后面的学习中详细学习改部分,这里不做详细讨论。

PortalGetPrimaryStmt 函数

  PortalGetPrimaryStmt 函数用于从语句列表中获取一个 “primary” 语句,也就是被标记为可以设置标签的语句。以下是该函数的解释:

  1. 该函数的主要作用是从一个语句列表中获取一个 “primary” 语句。“primary” 语句通常是带有结果标签tag)的语句,用于标识执行结果的类型
  2. 函数接受一个 stmts 参数,这是一个包含多个语句的列表。
  3. 函数使用 foreach 循环遍历语句列表中的每个语句。
  4. 对于每个语句,函数检查它的类型,可以是 PlannedStmtQueryPlannedStmt 通常用于执行计划,而 Query 通常用于查询
  5. 如果语句被标记为 canSetTag,则函数将返回该语句
  6. 如果语句不是 PlannedStmt不是 Query,则函数假定它是实用程序语句utility statement),并且只有在语句列表中只有一个语句时,才会被假定为可以设置标签。
  7. 如果遍历完整个列表后仍然没有找到 “primary” 语句,则函数返回 NULL

  PortalGetPrimaryStmt 函数源码如下:(路径:src\common\backend\utils\mmgr\portalmem.cpp

/*
 * PortalListGetPrimaryStmt
 *      获取一个 portal 中的“主要”语句,即标记为 canSetTag 的语句。
 *
 * 此函数从语句列表中检索主要语句。主要语句通常是可以设置结果标签的语句
 * (用于标识语句的结果类型)。
 *
 * 如果找不到这样的语句,则返回 NULL。如果在 portal 中有多个标记为 canSetTag
 * 的 PlannedStmt 或 Query 结构,则返回第一个。在当前使用中,不应出现这种情况。
 *
 * 此函数还可以处理 Query 列表,尽管在 portal 中不会发生这种情况。但此代码还支持
 * plancache.c,它可能需要处理这两种情况。
 *
 * 注意:直接传递 List 的原因是允许 plancache.c 共享此代码。对于 portal 使用,
 * 应使用 PortalGetPrimaryStmt 而不是直接调用此函数。
 */
Node* PortalListGetPrimaryStmt(List* stmts)
{
    ListCell* lc = NULL;

    foreach (lc, stmts) {
        Node* stmt = (Node*)lfirst(lc);

        if (IsA(stmt, PlannedStmt)) {
            if (((PlannedStmt*)stmt)->canSetTag)
                return stmt;
        } else if (IsA(stmt, Query)) {
            if (((Query*)stmt)->canSetTag)
                return stmt;
        } else {
            /* 如果只有一个语句,则假设实用程序语句可以设置标签 */
            if (list_length(stmts) == 1)
                return stmt;
        }
    }
    return NULL;
}

ExecTypeFromTLInternal 函数

  ExecTypeFromTLInternal 函数用于生成一个新的元组描述,该描述定义了结果集的结构,通常用于查询结果的处理。其函数源码如下:(路径:src\gausskernel\runtime\executor\execTuples.cpp

/* ----------------------------------------------------------------
 *		ExecCleanTypeFromTL
 *
 *		与上面的函数相同,但从结果中省略了 resjunk 列。
 * ----------------------------------------------------------------
 */
TupleDesc ExecCleanTypeFromTL(List* target_list, bool has_oid, TableAmType tam)
{
    // 调用内部函数 ExecTypeFromTLInternal,传入参数 skip_junk 为 true,省略 resjunk 列
    return ExecTypeFromTLInternal(target_list, has_oid, true, false, tam);
}

static TupleDesc ExecTypeFromTLInternal(List* target_list, bool has_oid, bool skip_junk, bool mark_dropped,  TableAmType tam)
{
    TupleDesc type_info;  // 用于存储元组描述信息
    ListCell* l = NULL;
    int len;  // 结果集中的列数
    int cur_resno = 1;  // 当前结果集列的序号

    // 根据 skip_junk 参数计算结果集的列数
    if (skip_junk)
        len = ExecCleanTargetListLength(target_list);
    else
        len = ExecTargetListLength(target_list);

    // 创建一个模板元组描述,用于存储结果集的结构
    type_info = CreateTemplateTupleDesc(len, has_oid, tam);

    // 遍历结果集的目标项列表
    foreach (l, target_list) {
        TargetEntry* tle = (TargetEntry*)lfirst(l);

        // 如果 skip_junk 为 true 且当前项是 resjunk(不需要的项),则跳过
        if (skip_junk && tle->resjunk)
            continue;

        // 初始化元组描述中的一个列项
        TupleDescInitEntry(
            type_info, cur_resno, tle->resname, exprType((Node*)tle->expr), exprTypmod((Node*)tle->expr), 0);
        TupleDescInitEntryCollation(type_info, cur_resno, exprCollation((Node*)tle->expr));

        /* 标记为已丢弃的列,也许将来可以找到另一种方法 */
        if (mark_dropped && strstr(tle->resname, "........pg.dropped.")) {
            type_info->attrs[cur_resno - 1]->attisdropped = true;
        }

        cur_resno++;
    }

    // 返回构建好的元组描述
    return type_info;
}

UtilityTupleDescripto 函数

  函数 UtilityTupleDescripto 用于获取一个实用程序语句(utility statement)的输出元组描述符Tuple Descriptor),前提是之前的 UtilityReturnsTuples() 函数返回了 “true”,即该实用程序语句返回元组。,用于获取一个实用程序语句utility statement)的输出元组描述符(Tuple Descriptor),前提是之前的 UtilityReturnsTuples() 函数返回了 “true”,即该实用程序语句返回元组。其函数源码如下:(路径:src\gausskernel\process\tcop\utility.cpp

/*
 * UtilityTupleDescriptor
 *		获取实用程序语句的实际输出元组描述符,前提是之前 UtilityReturnsTuples() 函数返回了 "true"。
 *
 * 返回的元组描述符在当前内存上下文中创建(或复制)。
 */
TupleDesc UtilityTupleDescriptor(Node* parse_tree)
{
    switch (nodeTag(parse_tree)) {
        case T_FetchStmt: {
            FetchStmt* stmt = (FetchStmt*)parse_tree;
            Portal portal;

            // 如果是 FETCH 语句,且不是 MOVE,则获取对应的 Portal 并返回其元组描述符
            if (stmt->ismove)
                return NULL;
            portal = GetPortalByName(stmt->portalname);
            if (!PortalIsValid(portal))
                return NULL; /* 不是我们的任务去引发错误 */
            return CreateTupleDescCopy(portal->tupDesc);
        }

        case T_ExecuteStmt: {
            ExecuteStmt* stmt = (ExecuteStmt*)parse_tree;
            PreparedStatement* entry = NULL;

            // 如果是 EXECUTE 语句,则获取对应的预处理语句并返回其结果元组描述符
            entry = FetchPreparedStatement(stmt->name, false, true);
            if (entry == NULL)
                return NULL; /* 不是我们的任务去引发错误 */
            return FetchPreparedStatementResultDesc(entry);
        }

        case T_ExplainStmt:
            // 如果是 EXPLAIN 语句,则返回其结果元组描述符
            return ExplainResultDesc((ExplainStmt*)parse_tree);

        case T_VariableShowStmt: {
            VariableShowStmt* n = (VariableShowStmt*)parse_tree;

            // 如果是 VARIABLE SHOW 语句,则获取相关的 PG 变量的结果元组描述符
            return GetPGVariableResultDesc(n->name);
        }

        default:
            return NULL;
    }
}

你可能感兴趣的:(PostgerSQL,postgresql,数据库)