PortalStart
声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了《PostgresSQL数据库内核分析》一书,OpenGauss1.1.0 的开源代码和《OpenGauss数据库源码解析》一书以及OpenGauss社区学习文档
概述
在【PostgreSQL内核学习(八)—— 查询执行(查询执行策略)】中我们了解到,Portal 的执行过程为:CreatePortal —> PortalDefineQuery —> PortalStart —> PortalRun —> PortalDrop
。而在【PostgreSQL内核学习(十一)—— (CreatePortal)】一文中,我们介绍了 CreatePortal 函数的执行过程。其中,PortalDefineQuery 函数用于为一个 Portal(查询计划的执行状态)定义查询,包括查询的源文本、命令标签、语句列表和缓存计划等属性。函数比较简单,这里不再做赘述。本文着重来继续学习 PortalStart 函数。
PortalStart 函数
PortalStart 函数的作用和意义是 启动一个 portal(查询计划的执行状态),使其准备好执行查询。以下是该函数的主要作用和意义:
- 启动 Portal:PortalStart 函数用于启动一个处于初始状态的 portal,将其状态从 PORTAL_DEFINED 更改为 PORTAL_READY,表示该 portal 已准备好执行查询。
- 资源分配:在启动 portal 之前,它可能需要进行一些资源的分配和初始化,例如为查询结果集分配内存空间、打开游标等。PortalStart 可以在这些资源准备完毕后将 portal 设置为 PORTAL_READY,以便执行查询。
- 查询执行:一旦 portal 被启动,可以调用 PortalRun 函数来执行查询。PortalStart 通常在执行查询之前调用,确保 portal 处于正确的状态。
- 查询计划的执行:如果 portal 中关联了查询计划(通过 CachedPlan),PortalStart 可能会执行查询计划,以准备执行查询。这包括对计划的解析、重写、优化和执行等步骤。
- 状态转换:PortalStart 的一个关键作用是将 portal 的状态从定义状态更改为准备状态,这是执行查询的先决条件。状态的正确管理对于查询的执行非常重要,因为不同状态下的 portal 具有不同的行为和要求。
PortalStart 函数的入参含义如下:
- portal:要启动的 Portal 对象,表示一个查询或命令的执行计划。
- params:ParamListInfo 类型的参数列表,用于传递参数值给查询或命令。这些参数通常包括绑定的参数值,允许在执行过程中将这些参数的值绑定到查询中。如果不需要参数,可以传入 NULL。
- eflags:整数,表示执行标志(Execution Flags),它是一组用于控制查询或命令执行过程的标志位。不同的标志位可以控制查询的行为,例如是否支持回滚、是否允许修改、是否需要获取锁等。具体的标志位取决于执行的需求,通常由上层代码根据情况设置。
- snapshot:Snapshot 类型,表示当前事务的快照。快照用于控制查询在哪个时间点看到数据库的数据,通常是用于隔离级别的控制。如果不需要使用快照,可以传入 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_SELECT、PORTAL_UTIL_SELECT、PORTAL_ONE_RETURNING 和 PORTAL_MULTI_QUERY。选择策略的主要依据是语句的数量、类型以及是否包含 RETURNING 子句。
其中,执行策略解释如下:
- PORTAL_ONE_SELECT(单个 SELECT 语句):
- 意义:该策略适用于仅包含一个 SELECT 查询的情况,而且查询不包含 RETURNING 子句。
- 使用场景:用于执行单个 SELECT 查询,通常不返回任何修改的行,也不需要返回任何结果集的行数。这是一个典型的只读查询。
- PORTAL_UTIL_SELECT(实用程序 SELECT):
- 意义:该策略适用于包含实用程序语句(例如 EXPLAIN 或 VACUUM)的情况,这些语句可能返回结果集。
- 使用场景:用于执行实用程序命令,可能返回结果集,但通常没有 RETURNING 子句。这允许执行查询和获取结果集。
- PORTAL_ONE_RETURNING(单个带 RETURNING 的查询):
- 意义:该策略适用于包含一个带 RETURNING 子句的查询,通常是修改语句(例如 INSERT, UPDATE 或 DELETE)。
- 使用场景:用于执行只修改一行或一组行的查询,并返回被修改的行。这个策略允许在修改数据的同时获取返回的数据。
- 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 函数的执行过程概括如下:
- 首先,函数接受一个参数 stmts,这是一个包含多个语句的列表(可能是查询、计划语句或实用程序命令)。
- 函数开始通过检查 stmts 中的内容来选择合适的 Portal 执行策略。
- 如果 stmts 中只包含一个元素,那么函数会检查这个元素的类型,并根据元素的性质和是否包含 RETURNING 子句来选择具体的执行策略。可能的情况包括:
- 如果是一个单个的 SELECT 查询且没有 RETURNING 子句,选择 PORTAL_ONE_SELECT 策略。
- 如果是一个单个的实用程序命令,且可能返回结果集,选择 PORTAL_UTIL_SELECT 策略。
- 如果是一个单个的带 RETURNING 子句的查询(通常是修改语句),选择 PORTAL_ONE_RETURNING 策略。
- 如果不满足以上条件,选择 PORTAL_MULTI_QUERY 作为通用策略。
- 如果 stmts 中包含多个元素,函数会遍历这些元素,计算满足特定条件的元素个数。如果只有一个满足条件的元素,且该元素具有 RETURNING 子句,则选择 PORTAL_ONE_RETURNING 策略。
- 如果以上条件都不满足,将选择 PORTAL_MULTI_QUERY 作为通用策略。
- 最终,函数返回选择的执行策略。
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* queryDesc 和 int eflags。
- queryDesc 参数是之前由 CreateQueryDesc 创建的查询描述对象。该对象包含了有关查询的信息,包括将返回的元组描述以及内部字段(estate 和 planstate)的设置。
- 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” 语句,也就是被标记为可以设置标签的语句。以下是该函数的解释:
- 该函数的主要作用是从一个语句列表中获取一个 “primary” 语句。“primary” 语句通常是带有结果标签(tag)的语句,用于标识执行结果的类型。
- 函数接受一个 stmts 参数,这是一个包含多个语句的列表。
- 函数使用 foreach 循环遍历语句列表中的每个语句。
- 对于每个语句,函数检查它的类型,可以是 PlannedStmt 或 Query。PlannedStmt 通常用于执行计划,而 Query 通常用于查询。
- 如果语句被标记为 canSetTag,则函数将返回该语句。
- 如果语句不是 PlannedStmt 也不是 Query,则函数假定它是实用程序语句(utility statement),并且只有在语句列表中只有一个语句时,才会被假定为可以设置标签。
- 如果遍历完整个列表后仍然没有找到 “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;
}
}