postgresql源码学习(五)—— 提交事务

结束事务分为两类:

  • 提交:CommitTransaction
  • 回滚(包含清理):AbortTransaction

由于比较长,我们分开两篇记录。

一、 CommitTransaction

1. gdb测试

会话1,隐式事务会自动提交

postgresql源码学习(五)—— 提交事务_第1张图片

会话2

postgresql源码学习(五)—— 提交事务_第2张图片

       跟之前一样,我们先只是过一遍这个函数流程,里面具体调用函数的步骤这里都会先跳过,避免深陷泥潭。Lets Go!

2. 具体代码与跟踪

调用栈如下

postgresql源码学习(五)—— 提交事务_第3张图片

/*
 *	CommitTransaction
 *
 * 注意:如果想修改此函数逻辑,最好同时也修改 PrepareTransaction 函数
 */
static void
CommitTransaction(void)
{
	TransactionState s = CurrentTransactionState; //事务栈
	TransactionId latestXid;

	bool		is_parallel_worker;

	is_parallel_worker = (s->blockState == TBLOCK_PARALLEL_INPROGRESS);

postgresql源码学习(五)—— 提交事务_第4张图片

       我看到这有点疑惑,之前的文章不是说隐式事务不会有INPROGRESS的状态吗?于是又回去翻了一下,噢,原来是隐式事务没有TBLOCK_INPROGRESS状态(事务块状态而不是事务状态)。这俩蛮容易弄混的,需要注意。postgresql源码学习(二)—— 事务块状态转换_Hehuyi_In的博客-CSDN博客

	/* 如果是并行worker提交,强制进入并行模式. */
	if (is_parallel_worker)
		EnterParallelMode();

	ShowTransactionState("CommitTransaction");

postgresql源码学习(五)—— 提交事务_第5张图片

/*
	 * 检查当前事务状态
	 */
	if (s->state != TRANS_INPROGRESS)
		elog(WARNING, "CommitTransaction while in %s state",
			 TransStateAsString(s->state));
	Assert(s->parent == NULL);

	/*
	 * Do pre-commit processing that involves calling user-defined code, such
	 * as triggers.  SECURITY_RESTRICTED_OPERATION contexts must not queue an
	 * action that would run here, because that would bypass the sandbox.
	 * Since closing cursors could queue trigger actions, triggers could open
	 * cursors, etc, we have to keep looping until there's nothing left to do.
* 执行涉及调用用户定义代码(如触发器)的预提交处理。
* 因为关闭游标可能会执行触发器,触发器可能打开游标等等,所以我们必须一直循环,直到没有什么可做的。
	 */
	for (;;)
	{
		/*
		 * 触发所有after触发器
		 */
		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.
* 关闭打开的portals(将holdable portals转换为static portals),portals好像是一种内存资源?
* 循环检查触发器队列直至全部关闭完
		 */
		if (!PreCommit_Portals(false))
			break;
	}

postgresql源码学习(五)—— 提交事务_第6张图片

/*
	 * The remaining actions cannot call any user-defined code, so it's safe
	 * to start shutting down within-transaction services.  But note that most
	 * of this stuff could still throw an error, which would switch us into
	 * the transaction-abort path.
     * 剩余的操作不能调用用户自定义的代码,因此可以安全地开始关闭事务内的服务。
     * 但是请注意,大多数这些动作仍然会抛出错误,这会导致流程切换到事务中止的路径上。
	 */

	CallXactCallbacks(is_parallel_worker ? XACT_EVENT_PARALLEL_PRE_COMMIT
					  : XACT_EVENT_PRE_COMMIT);

postgresql源码学习(五)—— 提交事务_第7张图片

/* 如果有并行workers, 需要清理 */
	if (IsInParallelMode())
		AtEOXact_Parallel(true);

	/* Shut down the deferred-trigger manager,关闭延迟触发器管理器 */
	AfterTriggerEndXact(true);

	/*
	 * Let ON COMMIT management do its thing (must happen after closing
	 * cursors, to avoid dangling-reference problems)
* 由ON COMMIT管理器执行(必须在关闭游标后执行,避免挂起引用问题)
	 */
	PreCommit_on_commit_actions();

	/*
	 * 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.
* 同步该事务创建的没有WAL日志记录的文件。这步必须在AtEOXact_RelationMap()前执行,避免在crash后我们看不到已提交但是broken了的文件
	 */
	smgrDoPendingSyncs(true, is_parallel_worker);

	/* close large objects before lower-level cleanup ,在低级别清理前关闭大对象 */
	AtEOXact_LargeObject(true);

	/*
	 * 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.
* 将NOTIFY命令发送的通知插入到队列中。这应该在预提交序列的末尾,以最小化持有notify-insertion锁的时间。然而这可能导致创建一个快照,因此必须在序列化清理前执行这步。
	 */
	PreCommit_Notify();

postgresql源码学习(五)—— 提交事务_第8张图片

/*
	 * 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();

	/* Prevent cancel/die interrupt while cleaning up,清理期间禁用中断,避免被打断 */
	HOLD_INTERRUPTS();

	/* Commit updates to the relation map --- do this as late as possible,提交更新到relation map -- 尽量晚地执行该动作 */
	AtEOXact_RelationMap(true, is_parallel_worker);

postgresql源码学习(五)—— 提交事务_第9张图片

注意这里有特别重要的两步:

  • 设置事务状态为已提交 s->state = TRANS_COMMIT;  
  • 将事务日志写回磁盘 RecordTransactionCommit(),关于这步,以后还要继续深入学习
	/*
	 * 设置事务状态
	 */
	s->state = TRANS_COMMIT;
	s->parallelModeLevel = 0;

	if (!is_parallel_worker)
	{
		/*
		 * We need to mark our XIDs as committed in pg_xact.  This is where we
		 * durably commit.将已提交的xid保存在pg_xact中(事务日志写回磁盘),这是我们持久化提交的位置
		 */
		latestXid = RecordTransactionCommit();
	}

postgresql源码学习(五)—— 提交事务_第10张图片

else
	{
		/*
		 * We must not mark our XID committed; the parallel leader is
		 * responsible for that.并行worker则不需要标记,由并行leader处理
		 */
		latestXid = InvalidTransactionId;

		/*
		 * Make sure the leader will know about any WAL we wrote before it
		 * commits.确保leader在提交之前知道worker写入的WAL
		 */
		ParallelWorkerReportLastRecEnd(XactLastRecEnd);
	}

	TRACE_POSTGRESQL_TRANSACTION_COMMIT(MyProc->lxid);

	/*
	 * Let others know about no transaction in progress by me. Note that this
	 * must be done _before_ releasing locks we hold and _after_
	 * RecordTransactionCommit.
* 通知其他进程,本进程中已没有进行中的事务。
     * 注意,这必须在释放持有的锁之前、RecordTransactionCommit之后执行。
	 */
	ProcArrayEndTransaction(MyProc, latestXid);

	/*
	 * This is all post-commit cleanup.  Note that if an error is raised here,
	 * it's too late to abort the transaction.  This should be just
	 * noncritical resource releasing.
* 这些都是提交后清理。
* 请注意,如果这里才出现错误终止事务就太迟了,这应该是非关键的资源释放。

	 * The ordering of operations is not entirely random.  The idea is:
	 * release resources visible to other backends (eg, files, buffer pins);
	 * then release locks; then release backend-local resources. We want to
	 * release locks at the point where any backend waiting for us will see
	 * our transaction as being fully cleaned up.
* 操作的顺序并不是完全随机的。
* 其思想是:先释放对其他后台进程可见的资源(如文件、buffer pins),然后释放锁,最后释放后端本地资源。
* 我们希望在所有等待本后台进程看到本事务被完全清理时才释放锁。

	 * Resources that can be associated with individual queries are handled by
	 * the ResourceOwner mechanism.  The other calls here are for backend-wide
	 * state.
* 与单个查询关联的资源由ResourceOwner机制处理。
* 这里的其他调用是针对后台进程范围状态的。
	 */

	CallXactCallbacks(is_parallel_worker ? XACT_EVENT_PARALLEL_COMMIT
					  : XACT_EVENT_COMMIT);

	ResourceOwnerRelease(TopTransactionResourceOwner,
						 RESOURCE_RELEASE_BEFORE_LOCKS,
						 true, true);

postgresql源码学习(五)—— 提交事务_第11张图片

/* Check we've released all buffer pins,检查所有已释放的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).
* 使目录更改对所有后台进程可见。
* 这必须发生在relcache引用被删除之后(参见AtEOXact_RelationCache注释),
* 但在锁被释放之前(如果有人在等待我们修改了的表的锁,我们希望他们在开始使用该表前知道目录的更改)。
	 */
	AtEOXact_Inval(true);

	AtEOXact_MultiXact();

	ResourceOwnerRelease(TopTransactionResourceOwner,
						 RESOURCE_RELEASE_LOCKS,
						 true, true);
	ResourceOwnerRelease(TopTransactionResourceOwner,
						 RESOURCE_RELEASE_AFTER_LOCKS,
						 true, true);

postgresql源码学习(五)—— 提交事务_第12张图片

	/*
	 * Likewise, dropping of files deleted during the transaction is best done
	 * after releasing relcache and buffer pins.  (This is not strictly
	 * necessary during commit, since such pins should have been released
	 * already, but this ordering is definitely critical during abort.)  Since
	 * this may take many seconds, also delay until after releasing locks.
	 * Other backends will observe the attendant catalog changes and not
	 * attempt to access affected files.
* 同样,在事务期间删除的文件的清理最好在释放relcache和buffer pin之后进行。(这在提交过程中并不是必须的,因为这样的pins应该已经被释放了,但是该顺序在中止过程中绝对是至关重要的。)
* 因为这可能需要较长的时间,所以也要延迟到释放锁之后。
* 其他后台进程将监控相关的catalog更改,不尝试访问受影响的文件。
	 */
	smgrDoPendingDeletes(true);

	AtCommit_Notify();

	//一大波资源清理
	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 = NULL;
	ResourceOwnerDelete(TopTransactionResourceOwner);
	s->curTransactionOwner = NULL;
	CurTransactionResourceOwner = NULL;
	TopTransactionResourceOwner = NULL;

	AtCommit_Memory();

postgresql源码学习(五)—— 提交事务_第13张图片

postgresql源码学习(五)—— 提交事务_第14张图片

// 重置事务栈变量
	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;

postgresql源码学习(五)—— 提交事务_第15张图片

/*
	 * done with commit processing, set current transaction state back to default,完成事务提交后,将当前事务状态改回TRANS_DEFAULT
	 */
	s->state = TRANS_DEFAULT;

	RESUME_INTERRUPTS(); //恢复中断
}

postgresql源码学习(五)—— 提交事务_第16张图片

3. 主要流程图

postgresql源码学习(五)—— 提交事务_第17张图片

二、 事务日志写回磁盘

       上面流程图里最重要的是事务日志写回磁盘部分:RecordTransactionCommit(),保证已提交数据不会丢失。

if ((wrote_xlog && markXidCommitted &&
		 synchronous_commit > SYNCHRONOUS_COMMIT_OFF) ||
		forceSyncCommit || nrels > 0) //判断是否要求同步提交,nrels > 0暂时没懂
	{
		XLogFlush(XactLastRecEnd); //如果是,则必须先写日志

		/*
		 * Now we may update the CLOG, if we wrote a COMMIT record above。更新CLOG,更新事务状态
		 */
		if (markXidCommitted)
			TransactionIdCommitTree(xid, nchildren, children);
	}
	else //如果是异步提交,不要求先写WAL,但崩溃时可能有数据丢失
	{
		/*设置异步提交最新的LSN		 */
		XLogSetAsyncXactLSN(XactLastRecEnd);

		/*
		 * 将最新的LSN保存到异步提交的事务组中,不要求先写WAL即可提交事务,同时将事务状态保存到CLOG中
		 */
		if (markXidCommitted)
			TransactionIdAsyncCommitTree(xid, nchildren, children, XactLastRecEnd);
	}

RecordTransactionCommit函数内的主要调用流程如下,本篇我们暂时不详细分析了

postgresql源码学习(五)—— 提交事务_第18张图片

参考:

《PostgreSQL技术内幕:事务处理深度探索》第1章

《PostgreSQL数据库内核分析》第7章

PostgreSQL 源码解读(122)- MVCC#7(提交事务-整体流程)_ITPUB博客

PostgreSQL 源码解读(123)- MVCC#8(提交事务-实际提交过程)_ITPUB博客

你可能感兴趣的:(源码学习,PostgreSQL,事务,源码,postgresql,事务,gdb,调试)