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


    相关
    《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

    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();
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    第二步:处理本地锁表

    	hash_seq_init(&status, LockMethodLocalHash);
    
    	while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
    	{
    		LOCALLOCKOWNER *lockOwners = locallock->lockOwners;
    		bool		haveSessionLock;
    		bool		haveXactLock;
    		int			i;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    VXID锁无需记录,VXID属于会话锁,这里只处理事务锁。

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

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

    		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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    		RemoveLocalLock(locallock);
    	}
    
    
    • 1
    • 2
    • 3

    第三步:开始扫主锁表

    遍历数据结构如下图:

    • 当前进程的PGPROC中有一个16个链表头组成的锁链数组,每一个位置上是一个PROCLOCK链表头。
    • 数组16个位置对应主锁表的16个分组,遍历全部主锁表 等价与 遍历这个数组上每个位置的链表。

    在这里插入图片描述

    	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);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    		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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    从procLink链表上删除这个PROCLOCK。

    			SHMQueueDelete(&proclock->procLink);
    
    
    • 1
    • 2

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

    			proclocktag.myLock = lock;
    			proclocktag.myProc = newproc;
    
    
    • 1
    • 2
    • 3

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

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

    			if (!hash_update_hash_key(LockMethodProcLockHash,
    									  (void *) proclock,
    									  (void *) &proclocktag))
    				elog(PANIC, "duplicate entry found while reassigning a prepared transaction's locks");
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    重新插入procLink链表。

    			/* 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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  • 相关阅读:
    《LeetCode力扣练习》代码随想录——数组(二分查找---Java)
    OpenCV图像处理学习五,图像的线性混合叠加
    C/C++内存管理
    P4491 [HAOI2018] 染色
    苹果双系统和虚拟机哪个好用?
    使用C语言实现动态顺序表
    【Azure Developer - 密钥保管库 】使用 Python Azure SDK 实现从 Azure Key Vault Certificate 中下载证书(PEM文件)
    vue3 - 项目集成vue-i18n国际化和Element Plus 国际化
    深度学习基础--神经网络(2)
    JDBC入门到精通-10w总结
  • 原文地址:https://blog.csdn.net/jackgo73/article/details/126465352