海山数据库(He3DB)源码详解:AbortSubTransaction函数
1、执行条件
-
在终端主动执行ABORT语句,终止事务会在CommitTransactionCommand函数中调用AbortSubTransaction函数;
-
在隐式事务中执行SQL语句发生错误时,底层源码会跳回PostgresMain函数中并调用AbortCurrentTransaction函数,终止事务并调用AbortSubTransaction函数。
2、执行过程
下面是一个简化的 AbortSubTransaction
函数执行流程的框架图:
这个流程图描述了从开始执行 AbortSubTransaction
命令到最终完成或发生错误的整个过程。每个步骤都是决策点或操作,其中:
- B 代表获取子事务的信息。
- C 是一个决策点,检查子事务是否存在。
- D 到 H 描述了正常的中止流程,包括锁定资源、回滚更改、释放资源和记录操作。
- I 是另一个决策点,检查在中止过程中是否发生错误。
- J 表示如果发生错误,则记录错误并尝试继续回滚。
- K 表示成功清理子事务状态。
- L 表示如果发生错误,则通知客户端中止中发生错误。
- M 表示通知客户端子事务已中止。
请注意,这个流程图是一个高层次的抽象,实际的 PostgreSQL 源码实现会更加复杂,并且涉及到许多底层的函数和数据结构,因此本文会在下一节详细解读函数源码。
3、源码解读
1. 开始终止事务
获取事务状态并关闭中断。
TransactionState s = CurrentTransactionState;
/* Prevent cancel/die interrupt while cleaning up */
HOLD_INTERRUPTS();
从全局变量中获得需要终止的事务状态,并关闭中断开始事务的清理工作。
2. 清理事务资源
1、检查上下文和资源owner
/* Make sure we have a valid memory context and resource owner */
AtSubAbort_Memory();
AtSubAbort_ResourceOwner();
-
AtSubAbort_Memory()函数将内存上下文切换到TransactionAbortcontext;
-
AtSubAbort_ResourceOwner()函数将全局变量CurrentResourceOwner切换为s->curTransactionowner。
2、释放LW锁和缓冲区资源
LWLockReleaseAll();
pgstat_report_wait_end();
pgstat_progress_end_command();
AbortBufferIO();
UnlockBuffers();
/* Reset WAL record construction state */
XLogResetInsertion();
-
调用LWLockReleaseAll()释放所有的LW轻量锁,但是不包括缓冲区锁(后面再释放);
-
调用pgstat_report_wait_end()将事件等待列表信息(my_wait_event_info)置为0;
-
调用pgstat_progress_end_command()函数向统计收集器(Statistics Collector)报告命令的进度信息同时更新统计信息,将st_progress_command字段设置为PROGRESS_COMMAND_INVALID,将st_progress_command_target字段设置为InvalidOid;
-
在事务发生错误后调用AbortBufferIO()清理任何活动的缓冲区 I/O 操作,之后调用UnlockBuffers()释放缓冲区锁;
-
重置WAL日志记录状态。
3、释放锁和信号量,重置信号操作
/* Cancel condition variable sleep */
ConditionVariableCancelSleep();
LockErrorCleanup();
reschedule_timeouts();
PG_SETMASK(&UnBlockSig);
-
调用ConditionVariableCancelSleep()取消所有被信号量控制,处于睡眠等待状态中的操作;
-
调用LockErrorCleanup()取消任何待处理的锁等待,如果事务在获取更强的锁(例如,从行锁升级到表锁)的过程中被终止,LockErrorCleanup 会撤销这个过程中对锁计数的增加;
-
调用reschedule_timeouts()函数重新安排任何待处理的 SIGALRM 中断;
-
调用PG_SETMASK(&UnBlockSig)重新启用信号,以防从信号处理器中 longjmp出来,为了让超时机制能够在需要的时候能够发挥作用。
4、检查当前状态并开始终止事务
/* check the current transaction state */
ShowTransactionState("AbortSubTransaction");
if (s->state != TRANS_INPROGRESS)
elog(WARNING, "AbortSubTransaction while in %s state",
TransStateAsString(s->state));
s->state = TRANS_ABORT;
-
调用ShowTransactionState(“AbortSubTransaction”)记录一条日志;
-
检查当前状态是否正确(应处在TRANS_INPROGRESS),错误则记录一条错误日志;
-
修改当前事务状态为TRANS_ABORT。
5、重置user ID、活动的REINDEX和逻辑流状态
/* Reset user ID which might have been changed transiently */
SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
/* Forget about any active REINDEX. */
ResetReindexState(s->nestingLevel);
/* Reset logical streaming state. */
ResetLogicalStreamingState();
-
调用SetUserIdAndSecContext(s->prevUser, s->prevSecContext)重置可能被临时更改的用户 ID,恢复SecurityRestrictionContext之前的值;
-
调用ResetReindexState(s->nestingLevel)恢复所有索引重置状态为默认值;
-
调用ResetLogicalStreamingState()重置逻辑流的状态到默认值。
6、检查事务并行状态并进行清理
/* Exit from parallel mode, if necessary. */
if (IsInParallelMode())
{
AtEOSubXact_Parallel(false, s->subTransactionId);
s->parallelModeLevel = 0;
}
-
调用IsInParallelMode()判断当前事务的并行状态;
-
如果在并行事务状态下,清理子事务的并行模式上下文资源,并将并行模式恢复默认状态;
7、清理触发器、游标、大对象,并通知事务终止
AfterTriggerEndSubXact(false);
AtSubAbort_Portals(s->subTransactionId,
s->parent->subTransactionId,
s->curTransactionOwner,
s->parent->curTransactionOwner);
AtEOSubXact_LargeObject(false, s->subTransactionId,
s->parent->subTransactionId);
AtSubAbort_Notify();
/* Advertise the fact that we aborted in pg_xact. */
(void) RecordTransactionAbort(true);
-
AfterTriggerEndSubXact()在子事务终止过程中,函数会将触发器到恢复默认状态,并清理等待中的事件列表;
-
AtSubAbort_Portals()函数会将该子事务创建或使用的游标全部停用,,释放portal对其缓存计划的引用和辅助上下文(subsidiary contexts),但是不会清理这些游标;
-
AtEOSubXact_LargeObject()函数在终止过程中,会将大文件的描述符直接关闭;
-
AtSubAbort_Notify()函数会清理掉在该子事务中的活动和通知,不进行出站通知。
-
RecordTransactionAbort(true)记录子事务被终止,并进行后台通知。
8、开始预终止的清理工作
/* Post-abort cleanup */
if (FullTransactionIdIsValid(s->fullTransactionId))
AtSubAbort_childXids();
CallSubXactCallbacks(SUBXACT_EVENT_ABORT_SUB, s->subTransactionId,
s->parent->subTransactionId);
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
false, false);
AtEOSubXact_RelationCache(false, s->subTransactionId,
s->parent->subTransactionId);
AtEOSubXact_Inval(false);
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_LOCKS,
false, false);
ResourceOwnerRelease(s->curTransactionOwner,
RESOURCE_RELEASE_AFTER_LOCKS,
false, false);
AtSubAbort_smgr();
AtEOXact_GUC(false, s->gucNestLevel);
AtEOSubXact_SPI(false, s->subTransactionId);
AtEOSubXact_on_commit_actions(false, s->subTransactionId,
s->parent->subTransactionId);
AtEOSubXact_Namespace(false, s->subTransactionId,
s->parent->subTransactionId);
AtEOSubXact_Files(false, s->subTransactionId,
s->parent->subTransactionId);
AtEOSubXact_HashTables(false, s->nestingLevel);
AtEOSubXact_PgStat(false, s->nestingLevel);
AtSubAbort_Snapshot(s->nestingLevel);
-
如果子事务XID有效,调用AtSubAbort_childXids()函数释放该事务的子事务XID;
-
调用AtEOSubXact_RelationCache()清理relation缓存;
-
调用AtEOSubXact_Inval()处理子事务的失效消息;
-
调用AtSubAbort_smgr()删除在子事务中创建的关系(例如,表或索引),并停止跟踪在子事务中标记为删除的关系。这意味着任何在子事务中创建的新关系都将被撤销,同时任何计划在子事务提交时删除的关系都将取消删除计划;
-
调用AtEOXact_GUC()函数处理数据库的GUC配置;
-
调用AtEOSubXact_SPI()函数处理SPI(Server Programming Interface,服务器编程接口)状态;
-
调用AtEOSubXact_on_commit_actions()函数针对
ON COMMIT
字句,立即删除在此子事务中创建的条目; -
调用AtEOSubXact_Namespace()函数调用重置 MyProc 中的临时命名空间标志;
-
调用AtEOSubXact_Files()函数关闭子事务可能打开的临时文件;
-
调用AtEOSubXact_HashTables()函数在子事务结束时进行清理任何仍然打开的散列表;
-
调用AtEOSubXact_PgStat()函数在子事务结束时更新或清理pgStat模块的状态;
-
调用AtSubAbort_Snapshot()函数清理事务快照。
9、恢复上层节点只读并恢复中断
XactReadOnly = s->prevXactReadOnly;
RESUME_INTERRUPTS();
- 恢复上层事务的只读状态,同时恢复中断机制;
- AbortSubTransaction函数执行完成,但是依旧有一些资源尚未清理,会在后续CleanupSubTransaction()函数中执行。
作者介绍
李超,移动云数据库工程师,负责云原生数据库He3DB的研发。
y = s->prevXactReadOnly;
RESUME_INTERRUPTS();
- 恢复上层事务的只读状态,同时恢复中断机制;
- AbortSubTransaction函数执行完成,但是依旧有一些资源尚未清理,会在后续CleanupSubTransaction()函数中执行。
作者介绍
李超,移动云数据库工程师,负责云原生数据库He3DB的研发。