Postgresql源码(74)两阶段事务PrepareTransaction事务如何与会话解绑(下)

2022-09-23 10:22:16 浏览数 (2)

相关 《Postgresql源码(69)常规锁简单分析》 《Postgresql源码(73)两阶段事务PrepareTransaction事务如何与会话解绑(上)》 《Postgresql源码(74)两阶段事务PrepareTransaction事务如何与会话解绑(下)》

1 两阶段事务使用的特殊PGPROC

InitProcGlobal时,申请的所有PGPROC如下图所示:

  • PGPROC数组分三段使用,数组入口在ProcGlobal->allProcs,数组申请在InitProcGlobal
  • 第一段:正常的后端服务进程
  • 第二段:5个,bgwriter、checkpointer、walwriter或startup、walsender、walreceiver。
  • 第三段:Dummy PROC两阶段事务专用,一阶段提交后,事务锁与第一段中的PGPROC解锁,与第三段中的PGPROC关联。

注意:

  • 在TwoPhaseShmemInit中,已经把PGPROC和GlobalTransactionData建立了一一对应关系。
  • 使用PGPROC是,只需要用GlobalTransactionData->pgprocnoallProcs里面找即可:proc = &ProcGlobal->allProcs[gxact->pgprocno];
  • 这个对应关系是一一对应的,初始化之后就不会改变了。

2 一阶段提交解锁流程

第一步:通过GlobalTransactionData拿到PGPROC

代码语言:javascript复制
void
PostPrepare_Locks(TransactionId xid)
{
	PGPROC	   *newproc = TwoPhaseGetDummyProc(xid, false);
	HASH_SEQ_STATUS status;
	LOCALLOCK  *locallock;
	LOCK	   *lock;
	PROCLOCK   *proclock;
	PROCLOCKTAG proclocktag;
	int			partition;
 
	START_CRIT_SECTION();

第二步:处理本地锁表

代码语言:javascript复制
	hash_seq_init(&status, LockMethodLocalHash);

	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
	{
		LOCALLOCKOWNER *lockOwners = locallock->lockOwners;
		bool		haveSessionLock;
		bool		haveXactLock;
		int			i;

VXID锁无需记录,一阶段提交完了会话就没了。

代码语言:javascript复制
		if (locallock->tag.lock.locktag_type == LOCKTAG_VIRTUALTRANSACTION)
			continue;
  • locallock->lockOwners是一个数组,初始8个值,每个值记录了一个Owner,数组记录了这把锁被多少个owner获取过。owner有值代表事务申请的锁、owner没值表示会话申请的锁。
  • 如果是sessionlock,表示在LockAcquire时的第三个参数为false,表示这把锁和会话关联,不和事务关联。
代码语言:javascript复制
		haveSessionLock = haveXactLock = false;
		for (i = locallock->numLockOwners - 1; i >= 0; i--)
		{
			if (lockOwners[i].owner == NULL)
				haveSessionLock = true;
			else
				haveXactLock = true;
		}

只留下事务锁,会话锁不是一阶段提交需要关心的。

代码语言:javascript复制
		if (!haveXactLock)
			continue;

		/* Mark the proclock to show we need to release this lockmode */
		if (locallock->nLocks > 0)
			locallock->proclock->releaseMask |= LOCKBIT_ON(locallock->tag.mode);

清理本地锁表,一阶段提交后,本地锁表不应该在保存事务锁了。

代码语言:javascript复制
		RemoveLocalLock(locallock);
	}

第三步:开始扫主锁表

遍历数据结构如下图:

  • 当前进程的PGPROC中有一个16个链表头组成的锁链数组,每一个位置上是一个PROCLOCK链表头。
  • 数组16个位置对应主锁表的16个分组,遍历全部主锁表 等价与 遍历这个数组上每个位置的链表。
代码语言:javascript复制
	for (partition = 0; partition < NUM_LOCK_PARTITIONS; partition  )
	{
		LWLock	   *partitionLock;
		SHM_QUEUE  *procLocks = &(MyProc->myProcLocks[partition]);
		PROCLOCK   *nextplock;

		partitionLock = LockHashPartitionLockByIndex(partition);
		
		// 优化:判断成功不用加锁
		if (SHMQueueNext(procLocks, procLocks,
						 offsetof(PROCLOCK, procLink)) == NULL)
			continue;			/* needn't examine this partition */

		LWLockAcquire(partitionLock, LW_EXCLUSIVE);

遍历每个分区的procLocks链表,拿到所有PROCLOCK。

代码语言:javascript复制
		for (proclock = (PROCLOCK *) SHMQueueNext(procLocks, procLocks,
												  offsetof(PROCLOCK, procLink));
			 proclock;
			 proclock = nextplock)
		{
			/* Get link first, since we may unlink/relink this proclock */
			nextplock = (PROCLOCK *)
				SHMQueueNext(procLocks, &proclock->procLink,
							 offsetof(PROCLOCK, procLink));

			lock = proclock->tag.myLock;

			if (lock->tag.locktag_type == LOCKTAG_VIRTUALTRANSACTION)
				continue;

			/* Ignore it if nothing to release (must be a session lock) */
			if (proclock->releaseMask == 0)
				continue;

从procLink链表上删除这个PROCLOCK。

代码语言:javascript复制
			SHMQueueDelete(&proclock->procLink);

lock没变,myProc换成两阶段事务专用dummyproc。

代码语言:javascript复制
			proclocktag.myLock = lock;
			proclocktag.myProc = newproc;

tag变了,用hash_update_hash_key更新主锁表LockMethodProcLockHash哈希表中的记录。

(注意主锁表LockMethodLockHash不需要改,因为tag和value都没变)

代码语言:javascript复制
			if (!hash_update_hash_key(LockMethodProcLockHash,
									  (void *) proclock,
									  (void *) &proclocktag))
				elog(PANIC, "duplicate entry found while reassigning a prepared transaction's locks");

重新插入procLink链表。

代码语言:javascript复制
			/* Re-link into the new proc's proclock list */
			SHMQueueInsertBefore(&(newproc->myProcLocks[partition]),
								 &proclock->procLink);

			PROCLOCK_PRINT("PostPrepare_Locks: updated", proclock);
		}						/* loop over PROCLOCKs within this partition */

		LWLockRelease(partitionLock);
	}							/* loop over partitions */

	END_CRIT_SECTION();
}

0 人点赞