相关 《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->pgprocno
在allProcs
里面找即可: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,表示这把锁和会话关联,不和事务关联。
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个分组,遍历全部主锁表
等价与
遍历这个数组上每个位置的链表。
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();
}