引言
在上一篇文章中,我学习了查询执行过程总体的函数调用,并对CreatePortal函数有了初步的了解。在本篇文章里,我将对Portal有关函数继续讲解。
执行过程(续前文)
CreatePortal
1.do{······}while(0)语句块
详解请见前文SQL 查询执行总体流程(1)。
2. 内存上下文管理
Portal结构体中的heap字段为MemoryContext(内存上下文)类型。
-
什么是内存上下文、为什么要使用内存上下文?
在opengauss数据库查询过程中,需要不断对某些任务分配内存,而这些分配的内存需要显示释放,因此很容易造成内存泄漏,经过时间的累计,存储空间会被耗尽。而内存上下文方法就应运而生了,对于某一个SQL语句,会给这个语句分配一个内存上下文,此SQL语句执行过程中,不再直接调用malloc函数申请内存,而是从内存上下文中申请。当SQL语句执行结束时,直接删除内存上下文,一次性释放所有内存。
-
内存上下文的逻辑结构
//代码清单1
typedef struct MemoryContextData* MemoryContext;
typedef struct MemoryContextData {
NodeTag type; /* 上下文类别*/
MemoryContextMethods* methods; /* 虛函数表*/
MemoryContext parent; /* 父上下文。顶级上下文为 NULL*/
MemoryContext firstchild; /* 子上下文的链表头*/
MemoryContext prevchild; /* 前向子上下文 */
MemoryContext nextchild; /* 后向子上下文 */
char* name; /* 上下文名称,方便调试 */
pthread_rwlock_t lock;
bool is_shared; /* 上下文是否在多个线程共享 */
bool isReset; /* 为true时表示复位后还未使用此内存上下文*/
int level; /* 上下文层次级别*/
uint64 session_id; /* 上下所属的会话ID */
ThreadId thread_id; /* 上下所属于的线程ID */
} MemoryContextData;
由代码清单1中的MemoryContextData定义可知,内存上下文是一个类似n叉树的树型结构。具体结构如图所示。
TopMemoryContext为顶层的内存上下文,其父结点为空。从结构图可以看出,父节点指向一个链表,链表的每一个结点又作为父节点,指向子链表。这样形成了一个链表嵌套链表的树形结构。
当删除某一个内存上下文时,只需要删除自身结点和子链表,并维护内存上下文树即可。
-
对内存上下文操作的函数
代码清单1第5行的虚函数表methods中存放对内存上下文操作的函数。
代码如下:
//代码清单2
typedef struct MemoryContextMethods {
/*在上下文中分配内存*/
void* (*alloc)(MemoryContext context, Size align, Size size, const char* file, int line);
/* 释放pointer 内存到上下文中*/
void (*free_p)(MemoryContext context, void* pointer);
/*在上下文中重新分配内存*/
void* (*realloc)(MemoryContext context, void* pointer, Size align, Size size, const char* file, int line);
void (*init)(MemoryContext context); /*上下文初始化*/
void (*reset)(MemoryContext context); /*上下文复位*/
void (*delete_context)(MemoryContext context); /*删除上下文 */
Size (*get_chunk_space)(MemoryContext context, void* pointer); /*获取上下文块大小 */
bool (*is_empty)(MemoryContext context); /*上下文是否为空*/
void (*stats)(MemoryContext context, int level); /*上下文信息统计*/
#ifdef MEMORY_CONTEXT_CHECKING
void (*check)(MemoryContext context); /*上下文异常检查*/
#endif
} MemoryContextMethods;
这些函数在SQL语句执行过程中进行晚期绑定,实际上,这些函数对应于AlignMemoryAllocator类中的AllocSet一族的函数。对应表如下。
函数指针 对应函数 alloc AllocSetAlloc free_p AllocSetFree realloc AllocSetRealloc init AllocSetInit reset AllocSetReset delete_context AllocSetDelete get_chunk_space AllocSetGetChunkSpace is_empty AllocSetIsEmpty check AllocSetCheck
PortalStart
PortalStart函数主要代码如代码清单3所示。
//代码清单3
void PortalStart(Portal portal, ParamListInfo params, int eflags, Snapshot snapshot)
{
············
/* Set up global portal context pointers.*/
saveActivePortal = ActivePortal;
saveResourceOwner = t_thrd.utils_cxt.CurrentResourceOwner;
savePortalContext = t_thrd.mem_cxt.portal_mem_cxt;
PG_TRY();
{
············
/* Determine the portal execution strategy*/
portal->strategy = ChoosePortalStrategy(portal->stmts);
/* Fire her up according to the strategy*/
switch (portal->strategy) {
case PORTAL_ONE_SELECT: {
············
/* Create QueryDesc in portal's context; for the moment, set
* the destination to DestNone.
*/
queryDesc = CreateQueryDesc(
ps, portal->sourceText, tempSnap, InvalidSnapshot, None_Receiver, params, 0, mot_jit_context);
#else
/* Create QueryDesc in portal's context; for the moment, set
* the destination to DestNone.
*/
queryDesc = CreateQueryDesc(
ps, portal->sourceText, GetActiveSnapshot(), InvalidSnapshot, None_Receiver, params, 0);
#endif
/* Call ExecutorStart to prepare the plan for execution*/
ExecutorStart(queryDesc, myeflags);
/* This tells PortalCleanup to shut down the executor*/
portal->queryDesc = queryDesc;
/* Remember tuple descriptor (computed by ExecutorStart)*/
portal->tupDesc = queryDesc->tupDesc;
············
break;
}
case PORTAL_ONE_RETURNING:
case PORTAL_ONE_MOD_WITH:
{
portal->tupDesc = ExecCleanTypeFromTL(pstmt->planTree->targetlist, false, TAM_HEAP);
}
············
break;
case PORTAL_UTIL_SELECT:
{
portal->tupDesc = UtilityTupleDescriptor(ustmt);
}
············
break;
case PORTAL_MULTI_QUERY:
············
break;
default:
break;
}
portal->stmtMemCost = 0;
}
PG_CATCH();
{
············
ActivePortal = saveActivePortal;
t_thrd.utils_cxt.CurrentResourceOwner = saveResourceOwner;
t_thrd.mem_cxt.portal_mem_cxt = savePortalContext;
}
PG_END_TRY();
MemoryContextSwitchTo(oldContext);
············
}
PortalStart函数主要作用是启动一个portal,确保查询所需的资源和状态都已经准备好。
主要功能如下:
- 初始化相关变量,然后保存portal上下文相关指针,以便执行异常时可以恢复。
- 根据不同的SQL语句,选择不同的执行策略。
- PORTAL_ONE_SELECT 策略:用于执行只包含一个SELECT语句的情况,并且查询结果不需要修改。
- PORTAL_ONE_RETURNING 和 PORTAL_ONE_MOD_WITH 策略:用于修改语句(INSERT、UPDATE、DELETE),允许修改数据的同时获取返回的数据。
- PORTAL_UTIL_SELECT 策略:用于程序语句(EXPLAIN和VACUUM)。
- PORTAL_MULTI_QUERY 策略:用于执行复杂的查询计划。
其代码逻辑如图所示:
小结
本篇文章里,我学习了内存上下文的逻辑结构及其作用,并学习了PortalStart函数的大致功能。下一篇文章我将继续对Portal部分的PortalRun函数做简要解析。