文章目录
海山数据库(He3DB)源码详解:CommitTransaction函数
本文介绍了事务提交过程中,具体执行提交任务的CommitTransaction函数详细执行流程。
1. 执行条件
- 当事务处于TRANS_INPROGRESS状态且事务块处于TBLOCK_STRATED或者TBLOCK_END状态下,由CommitTransactionCommand函数调用CommitTransaction函数完成提交事务。
- 在ParallelWorkerMain函数中由EndParallelWorkerTransaction调用。
2. 执行过程
2.1 获取当前节点状态:
获得当前节点变量值CurrentTransactionState,
并创建两个临时变量latestXid和is_parallel_worker。
TransactionState s = CurrentTransactionState;
TransactionId latestXid;
bool is_parallel_worker;
- CurrentTransactionState主要保存了当前事务状态相关的参数,主要是事务状态、事务块状态、XID、保存点、父节点等级等一系列参数。
- latestXid用来传递保存XID值。
- is_parallel_worker主要用于判断当前事务是否使用并行执行(并行执行指的是数据库将某些查询操作分布到多个工作进程中执行,以提高性能)。
2.2 检查当前状态:
检查并行执行模式,检查当前事务状态并判断父节点状态。
/* Enforce parallel mode restrictions during parallel worker commit. */
if (is_parallel_worker)
EnterParallelMode();
ShowTransactionState("CommitTransaction");
/*
* check the current transaction state
*/
if (s->state != TRANS_INPROGRESS)
elog(WARNING, "CommitTransaction while in %s state",
TransStateAsString(s->state));
Assert(s->parent == NULL);
- 如果执行并行执行模式,调用EnterParallelMode函数执行++s->parallelModeLevel。
- 检查当前事务状态,如果不是TRANS_INPROGRESS,则执行elog函数保存并返回主线程PostgreMain,执行Abort过程。
- 断言检查当前父节点状态,出错直接退出(调试用的)。
2.3 预提交处理:
第一、通过死循环处理用户定义的一些延迟触发器和一些portal操作。
for (;;)
{
/*
* Fire all currently pending deferred triggers.
*/
AfterTriggerFireDeferred();
/*
* Close open portals (converting holdable ones into static portals)
* If there weren't any, we are done. otherwise loop back to check
* if they queued deferred triggers. Lather, rinse, repeat.
*/
if (!PreCommit_Portals(false))
break;
}
这里的死循环行为是:因为,事务(增删改)操作完成之后会触发定义的延迟触发器,进行一些其他的行为,而这些其他行为中可能会包括游标的增删改查,导致了一个套娃操作。所以,这里的AfterTriggerFireDeferred函数处理延迟触发器,PreCommit_Portals处理游标,直到触发器处理完且没有游标需要处理,跳出函数。
- 不断调用AfterTriggerFireDeferred函数,在当前事务提交之前调用,执行并处理掉所有待处理的延迟触发器。
- 通过判断PreCommit_Portals函数的执行结果,来选择是否跳出死循环。PreCommit_Portals函数的作用,首先是将本次事务过程中的持久化游标物质化,并关闭游标对应需要的执行器,释放持有的锁;然后关闭本次事务过程中的一些非持久门户(Non-holdable portals),其他事务留存下来的持久性门户(Portals)不做改变。如果游标没有做任何操作,即该事务没有其他游标操作,返回false,否则返回true。
持久化游标物质化是指将持久化游标的结果集存储到磁盘或内存中,确保之后的游标查询结果不受后续事务操作对数据的影响。
第二、通过回调函数关闭参数对应的事务内部服务。
CallXactCallbacks(is_parallel_worker ? XACT_EVENT_PARALLEL_PRE_COMMIT
: XACT_EVENT_PRE_COMMIT);
第三、清理并行执行的资源,清理所有的触发器。
/* If we might have parallel workers, clean them up now. */
if (IsInParallelMode())
AtEOXact_Parallel(true);
/* Shut down the deferred-trigger manager */
AfterTriggerEndXact(true);
第四、在关闭游标后调用PreCommit_on_commit_actions函数。
执行ON COMMIT子句定义的行为,包括对临时表的处理和游标的处理等,确保之前的游标都被正确的关闭,不会造成悬空引用(类似于悬空指针)。
/*
* Let ON COMMIT management do its thing (must happen after closing
* cursors, to avoid dangling-reference problems)
*/
PreCommit_on_commit_actions();
第五、调用smgrDoPendingSyncs函数和AtEOXact_LargeObject函数。
/*
* Synchronize files that are created and not WAL-logged during this
* transaction. This must happen before AtEOXact_RelationMap(),
* so that we don't see committed-but-broken files after a crash.
*/
smgrDoPendingSyncs(true, is_parallel_worker);
/* close large objects before lower-level cleanup */
AtEOXact_LargeObject(true);
-
smgrDoPendingSyncs函数会在当前事务期间,对那些被创建且其更改没有记录在 WAL(Write-Ahead Logging)日志中的文件进行同步。这是为了确保如果发生崩溃,这些文件的状态是一致的,并且不会留下已提交但损坏的文件。
-
AtEOXact_LargeObject函数主要是清理一些超出常规字段所能存储限制的“大对象”,关闭与大型对象关联的文件描述符(LO fds),并清空 cookie 数组。
这样做的结果是,这些文件描述符将不再有效,无法再被用来访问或操作大型对象。无论事务是提交还是中止,持有这些 LO fds 的内存上下文和资源所有者在事务结束时都将被销毁。这意味着这些资源在事务结束时无论如何都会被清理。
在事务提交的情况下,需要关闭这些 LO fds,以避免在提交时出现关于资源泄露的警告。这是因为在提交时,系统会检查所有资源是否已经被正确管理,包括打开的文件描述符。
在事务中止的情况下,可以跳过关闭 LO fds 的步骤。这是因为在中止事务时,系统会回滚所有更改,并且通常不需要执行与提交相同的清理操作。
第六、调用PreCommit_Notify函数处理事务提交前与NOTIFY命令相关的操作。
它确保了NOTIFY命令能够在事务提交时正确地通知其他会话或应用程序。
/*
* Insert notifications sent by NOTIFY commands into the queue.This
* should be late in the pre-commit sequence to minimize time spent
* holding the notify-insertion lock.However, this could result in
* creating a snapshot,so we must do it before serializable cleanup.
*/
PreCommit_Notify();
第七、判断当前是否为并行执行模式。
False,执行PreCommit_CheckForSerializationFailure 函数,在事务准备提交时,检查当前事务是否可能与其他事务发生了冲突。如果检测到冲突,函数会抛出一个错误,导致当前事务回滚。(这里主要发生在并发执行情况下或者多个表的复杂查询事务)
通过这种方式,PostgreSQL 确保事务的串行化执行,避免数据不一致的问题。
/*
* Mark serializable transaction as complete for predicate locking
* purposes.This should be done as late as we can put it and still allow
* errors to be raised for failure patterns found at commit.This is not
* appropriate in a parallel worker however,because we aren't committing
* the leader's transaction and its serializable state will live on.
*/
if (!is_parallel_worker)
PreCommit_CheckForSerializationFailure();
- 串行化是事务隔离级别中最高的级别,它确保事务的执行就像它们是串行执行的一样,避免了所有并发问题。
- 可序列化事务(Serializable Transaction):可序列化事务是数据库事务的最高隔离级别,它通过完全串行化事务的执行来避免所有类型的并发问题,如脏读、不可重复读和幻读。
- 谓词锁定(Predicate Locking):谓词锁定是一种数据库锁定技术,用于锁定满足特定条件的数据行,而不是锁定整个表或数据页。这允许更细粒度的并发控制。
第八、关闭中断机制,清理重置映射,正式开始提交事务清理资源。
/* Prevent cancel/die interrupt while cleaning up */
HOLD_INTERRUPTS();
/* Commit updates to the relation map --- do this as late as possible */
AtEOXact_RelationMap(true, is_parallel_worker);
2.4 提交处理:
第一、修改事务状态,开始事务提交。
/*
* set the current transaction state information appropriately during
* commit processing
*/
s->state = TRANS_COMMIT;
s->parallelModeLevel = 0;
第二、根据并行事务状态,处理日志。
检查并行执行状态。不是并行事务,调用RecordTransactionCommit函数记录当前XID的日志。是并行事务,则调用ParallelWorkerReportLastRecEnd函数处理WAL日志,将当前XID置为InvalidTransactionId。
if (!is_parallel_worker)
{
/*
* We need to mark our XIDs as committed in pg_xact.This is where we
* durably commit.
*/
latestXid = RecordTransactionCommit();
}
else
{
/*
* We must not mark our XID committed; the parallel leader is
* responsible for that. */
latestXid = InvalidTransactionId;
/*
* Make sure the leader will know about any WAL we wrote before it
* commits. */
ParallelWorkerReportLastRecEnd(XactLastRecEnd);
}
ParallelWorkerReportLastRecEnd函数的目的是确保领导者进程在我们提交事务之前知道工作者进程已经写入的所有 WAL 日志。这是必要的,因为在并行事务处理中,领导者进程需要等待所有工作者进程完成它们的任务,并且记录了它们所有的更改,然后才能安全地提交整个事务。
第三、通知系统当前进程(MyProc)已经结束了一个事务。
ProcArrayEndTransaction 函数用于通知系统当前进程(MyProc)已经结束了一个事务,并且提供了要结束的事务 XID(latestXid)。ProcArrayEndTransaction 调用的顺序要求:
- 必须在释放当前进程持有的锁之前执行。这是因为在锁释放之前,其他进程需要知道当前进程已经结束了事务,以避免潜在的死锁或资源争用问题。
- 同时,它也必须在 RecordTransactionCommit 调用之后执行。RecordTransactionCommit 函数用于记录事务提交到 WAL(Write-Ahead Logging)日志,确保事务的持久性。
ProcArrayEndTransaction(MyProc, latestXid);
ProcArrayEndTransaction函数标记了一个事务不在运行。从这一步以后,即便出错也来不及阻止事务提交了,后续都是资源的释放和参数重置过程。
2.5 释放资源:
第一、释放TopTransactionResourceOwner所拥有的资源。
- AtEOXact_Buffers函数清理缓冲区。
- AtEOXact_RelationCache函数清理关系缓存。
- AtEOXact_Inval函数在主事务结束时处理排队中的无效信息。
- AtEOXact_MultiXact函数结束多事务。
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
true, true);
/* Check we've released all buffer pins */
AtEOXact_Buffers(true);
/* Clean up the relation cache */
AtEOXact_RelationCache(true);
/*
* Make catalog changes visible to all backends.This has to happen after
* relcache references are dropped (see comments for
* AtEOXact_RelationCache), but before locks are released (if anyone is
* waiting for lock on a relation we've modified, we want them to know
* about the catalog change before they start using the relation).
*/
AtEOXact_Inval(true);
AtEOXact_MultiXact();
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_LOCKS,
true, true);
ResourceOwnerRelease(TopTransactionResourceOwner,
RESOURCE_RELEASE_AFTER_LOCKS,
true, true);
ResourceOwnerRelease函数释放参数TopTransactionResourceOwner及其后代拥有的所有资源,但不删除“资源Owner”对象本身。
第二、处理删除操作并通知事务成功完成。
smgrDoPendingDeletes(true);
/*
* Send out notification signals to other backends (and do other
* post-commit NOTIFY cleanup). This must not happen until after our
* transaction is fully done from the viewpoint of other backends.
*/
AtCommit_Notify();
第三、依次清理资源。
- AtEOXact_GUC(true, 1);
处理:运行时配置参数(GUC)。
作用:在事务结束时应用或撤销事务期间设置的配置更改。 - AtEOXact_SPI(true);
处理:服务器过程接口(SPI)。
作用:清理 SPI 在事务中使用的资源和状态。 - AtEOXact_Enum();
处理:枚举类型。
作用:管理枚举类型的元数据,确保事务结束后枚举状态是一致的。 - AtEOXact_on_commit_actions(true);
处理:提交时执行的动作。
作用:执行在事务提交时需要进行的特定动作或回调。 - AtEOXact_Namespace(true, is_parallel_worker);
处理:命名空间。
作用:清理命名空间相关的资源,可能与并行工作有关。 - AtEOXact_SMgr();
处理:存储管理器。
作用:管理文件和存储相关的资源,如释放锁或处理文件删除。 - AtEOXact_Files(true);
处理:文件资源。
作用:清理事务中使用的文件,如临时文件。 - AtEOXact_ComboCid();
处理:组合命令 ID。
作用:管理与组合命令 ID 相关的事务状态。 - AtEOXact_HashTables(true);
处理:哈希表。
作用:清理或刷新哈希表,确保数据结构的一致性。 - AtEOXact_PgStat(true, is_parallel_worker);
处理:pg_stat 统计信息。
作用:更新统计收集模块的状态,可能与并行工作有关。 - AtEOXact_Snapshot(true, false);
处理:事务快照。
作用:清理事务快照,确保 MVCC 状态下的数据可见性正确。 - AtEOXact_ApplyLauncher(true);
处理:逻辑复制应用启动器。
作用:管理逻辑复制相关的事务结束时的资源和状态。 - pgstat_report_xact_timestamp(0);
处理:事务时间戳统计。
作用:报告事务的时间戳信息给 pg_stat 模块,0 可能表示当前时间或其他特定值。
/*
* Everything after this should be purely internal-to-this-backend
* cleanup.
*/
AtEOXact_GUC(true, 1);
AtEOXact_SPI(true);
AtEOXact_Enum();
AtEOXact_on_commit_actions(true);
AtEOXact_Namespace(true, is_parallel_worker);
AtEOXact_SMgr();
AtEOXact_Files(true);
AtEOXact_ComboCid();
AtEOXact_HashTables(true);
AtEOXact_PgStat(true, is_parallel_worker);
AtEOXact_Snapshot(true, false);
AtEOXact_ApplyLauncher(true);
pgstat_report_xact_timestamp(0);
第四、重置全局变量和状态变量。
重置CurrentResourceOwner、XactTopFullTransactionId、CurTransactionResourceOwner、TopTransactionResourceOwner、nParallelCurrentXids和事务状态变量TransactionState s。ResourceOwnerDelete函数会删除TopTransactionResourceOwner参数的对象及其后代。
CurrentResourceOwner = NULL;
ResourceOwnerDelete(TopTransactionResourceOwner);
s->curTransactionOwner = NULL;
CurTransactionResourceOwner = NULL;
TopTransactionResourceOwner = NULL;
AtCommit_Memory();
s->fullTransactionId = InvalidFullTransactionId;
s->subTransactionId = InvalidSubTransactionId;
s->nestingLevel = 0;
s->gucNestLevel = 0;
s->childXids = NULL;
s->nChildXids = 0;
s->maxChildXids = 0;
XactTopFullTransactionId = InvalidFullTransactionId;
nParallelCurrentXids = 0;
2.6 提交事务:
修改事务状态为TRANS_DEFAULT,放开中断机制,完成事务提交。
s->state = TRANS_DEFAULT;
RESUME_INTERRUPTS();
作者介绍
李超,移动云数据库工程师,负责云原生数据库He3DB的研发。