postgresql源码学习(十二)—— 常规锁⑤-事务等待

67 篇文章 52 订阅
20 篇文章 0 订阅

一、 等待机制

       如果通过冲突检测发现可以获得锁,则通过GrantLock和GrantLocalLock函数增加锁的引用计数。如果发现不能获得锁,则进入等待状态。

       等待状态的判断通过WaitOnLock函数实现,调用关系是WaitOnLock函数 -> ProcSleep函数。ProcSleep函数一方面将当前事务加入等待队列,另一方面还要做死锁检测。

二、 ProcSleep函数

1. 函数主要流程图

补画了一个流程图,可以对照着看,之前看着看着下面代码的判断和循环就晕菜了。

2. 具体代码

函数调用栈如下:

/*
 * ProcSleep -- put a process to sleep on the specified lock
 * ProcSleep -- 让进程等待特定的锁

 * Caller must have set MyProc->heldLocks to reflect locks already held
 * on the lockable object by this process (under all XIDs).
 * 调用者必须设置MyProc->heldLocks 表示本进程已获得的锁

 * The lock table's partition lock must be held at entry, and will be held
 * at exit.锁表的分区锁必须在进入函数时必须持有,在退出函数时也会持有
  

 * Result: PROC_WAIT_STATUS_OK if we acquired the lock, PROC_WAIT_STATUS_ERROR if not (deadlock).成功获得锁则返回PROC_WAIT_STATUS_OK,失败则返回PROC_WAIT_STATUS_ERROR(出现死锁)
 *
 * ASSUME: that no one will fiddle with the queue until after we release the partition lock. 假设没有人会篡改该等待队列,直到我们释放分区锁
 *
 * NOTES: The process queue is now a priority queue for locking.该进程队列目前是对锁优先队列
 */
ProcWaitStatus
ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable)
{
	LOCKMODE	lockmode = locallock->tag.mode;
	LOCK	   *lock = locallock->lock;
	PROCLOCK   *proclock = locallock->proclock;
	uint32		hashcode = locallock->hashcode;
	LWLock	   *partitionLock = LockHashPartitionLock(hashcode);
	PROC_QUEUE *waitQueue = &(lock->waitProcs);
	LOCKMASK	myHeldLocks = MyProc->heldLocks;
	TimestampTz standbyWaitStart = 0;
	bool		early_deadlock = false;
	bool		allow_autovacuum_cancel = true;
	bool		logged_recovery_conflict = false;
	ProcWaitStatus myWaitStatus;
	PGPROC	   *proc;
	PGPROC	   *leader = MyProc->lockGroupLeader;
	int			i;

	/*
	 * 收集当前事务在locallock这个锁对象上持有的其他锁模式至 myHeldLocks。如果不是单进程事务(有并行),则需要将相同group中持有的该锁对象的锁模式一并收集。
	 */
	if (leader != NULL) //不是单进程事务
	{
		SHM_QUEUE  *procLocks = &(lock->procLocks);
		PROCLOCK   *otherproclock;

		otherproclock = (PROCLOCK *)
			SHMQueueNext(procLocks, procLocks, offsetof(PROCLOCK, lockLink));
		while (otherproclock != NULL)
		{
			if (otherproclock->groupLeader == leader)
				myHeldLocks |= otherproclock->holdMask;
			otherproclock = (PROCLOCK *)
				SHMQueueNext(procLocks, &otherproclock->lockLink,
							 offsetof(PROCLOCK, lockLink));
		}
	}
/*
	 * Determine where to add myself in the wait queue.
	 * 决定将本事务加入到等待队列的哪个位置

	 * Normally I should go at the end of the queue.  However, if I already
	 * hold locks that conflict with the request of any previous waiter, put
	 * myself in the queue just in front of the first such waiter. This is not
	 * a necessary step, since deadlock detection would move me to before that
	 * waiter anyway; but it's relatively cheap to detect such a conflict
	 * immediately, and avoid delaying till deadlock timeout.
* 将当前事务(或进程)加入等待队列也是有技巧的。通常,等待队列应该按照锁申请的顺序排列,将当前事务加入等待队列的队尾。但如果本事务A除了当前申请的锁模式,已经持有了该对象的其他锁模式,而等待队列中的事务B等待的锁模式与事务A持有的锁模式冲突,此时再将事务A插入等待者B的后面,就隐含死锁的风险,可以考虑将事务A插队到事务B的前面。
	 */

	if (myHeldLocks != 0) //当前事务在这个锁对象上还持有其他锁模式
	{
		LOCKMASK	aheadRequests = 0;

// 从等待队列中获取一个事务,它是一个等待者
		proc = (PGPROC *) waitQueue->links.next;
		for (i = 0; i < waitQueue->size; i++) //循环检查等待队列
		{
			/*
			 * If we're part of the same locking group as this waiter, its
			 * locks neither conflict with ours nor contribute to
			 * aheadRequests. 如果当前事务(进程)跟等待队列的事务是同一个group的并行进程,那么它们之间不会有锁冲突,也不会对aheadRequests有什么用。取等待队列中的下一个事务,进入下一次循环即可。
			 */
			if (leader != NULL && leader == proc->lockGroupLeader)
			{
				proc = (PGPROC *) proc->links.next;
				continue;
			}
			/* Must he wait for me? 如果非并行进程,判断等待者想要的锁模式与当前事务持有的是否冲突。即,它一定要等我吗*/
			if (lockMethodTable->conflictTab[proc->waitLockMode] & myHeldLocks)
			{
				/* Must I wait for him ? 反过来,我等的是不是这个等待队列的事务持有的锁模式,我一定要等它吗*/
				if (lockMethodTable->conflictTab[lockmode] & proc->heldLocks)
				{
					/*
					 * Yes, so we have a deadlock.  Easiest way to clean up
					 * correctly is to call RemoveFromWaitQueue(), but we
					 * can't do that until we are *on* the wait queue. So, set
					 * a flag to check below, and break out of loop.  Also,
					 * record deadlock info for later message.
                       * 如果两者都是,那就死锁了。最简单的方式是调用RemoveFromWaitQueue清理,但必须等到我们*在*等待队列时才能执行,因此,我们只是打一个死锁的标记,然后退出循环。另外,还要记录死锁信息。
					 */
					RememberSimpleDeadLock(MyProc, lockmode, lock, proc);
					early_deadlock = true;
					break;
				}
				 /* I must go before this waiter. 我必须在它之前。
* aheadRequests 记录的是在等待队列中,处于proc这个等待者前面,所有等待者等待的模式的并集
* 如果当前锁模式和proc前面所有的锁模式都不冲突,且在主锁表也未检查到冲突,则获取锁成功
*/

				if ((lockMethodTable->conflictTab[lockmode] & aheadRequests) == 0 &&
					!LockCheckConflicts(lockMethodTable, lockmode, lock,
										proclock))
				{
					/* Skip the wait and just grant myself the lock. 跳过等待直接获取锁 */
					GrantLock(lock, proclock, lockmode);
					GrantAwaitedLock();
					return PROC_WAIT_STATUS_OK;
				}
				/* Break out of loop to put myself before him. 否则,要找到等待队列中第一个与我冲突的事务,记住这个proc,跳出循环,把当前事务(我)插到它前面 */
				break;
			}
			/* Nope, so advance to next waiter,如果它不需要等待我,那么再换下一个等待队列的事务 */
			aheadRequests |= LOCKBIT_ON(proc->waitLockMode); // 收集等待队列中等待的锁模式的并集
			proc = (PGPROC *) proc->links.next; //再换下一个等待队列的事务
		}

		/*
		 * If we fall out of loop normally, proc points to waitQueue head, so
		 * we will insert at tail of queue as desired.
* 如果这么多个if都没匹配到,循环顺利完成了,此时proc会指向等待队列waitQueue的尽头,因此我们可以按照之前预期的,将当前事务插到等待队列的最末尾。
		 */
	}
	else //如果当前事务没持有过锁对象的锁模式,获取等待队列的队首元素。也就是说,要把当前事务插到等待队列的头部。
	{
		/* I hold no locks, so I can't push in front of anyone. */
		proc = (PGPROC *) &(waitQueue->links);
	}

	/*
	 * Insert self into queue, ahead of the given proc (or at tail of queue). 将其插入到proc前面,或者队列最末端
	 */
	SHMQueueInsertBefore(&(proc->links), &(MyProc->links));
	waitQueue->size++; //等待队列长度加1

// 插入等待队列后,事务就开始进入等待状态
	lock->waitMask |= LOCKBIT_ON(lockmode);

	/* Set up wait information in PGPROC object, too */
	MyProc->waitLock = lock;
	MyProc->waitProcLock = proclock;
	MyProc->waitLockMode = lockmode;

	MyProc->waitStatus = PROC_WAIT_STATUS_WAITING;

	/*
	 * If we detected deadlock, give up without waiting.  This must agree with
	 * CheckDeadLock's recovery code.如果前面设置了early_deadlock标志,不需要再等待,直接返回报错
	 */
	if (early_deadlock)
	{
		RemoveFromWaitQueue(MyProc, hashcode);
		return PROC_WAIT_STATUS_ERROR;
	}

	/* mark that we are waiting for a lock */
	lockAwaited = locallock;

	/*
	 * Release the lock table's partition lock.
	 *
	 * NOTE: this may also cause us to exit critical-section state, possibly
	 * allowing a cancel/die interrupt to be accepted. This is OK because we
	 * have recorded the fact that we are waiting for a lock, and so
	 * LockErrorCleanup will clean up if cancel/die happens.
	 */
	LWLockRelease(partitionLock);

      这个函数下面还有很长一截关于InHotStandby状态下的处理,由于过于复杂,这里先跳过了。

等待状态的事务会在两种状态下被唤醒:

  • 死锁检测触发超时机制,要进行下一轮死锁检测
  • PGPROC->waitStatus不再是STATUS_WAITING状态,已经有其他事务释放了锁,当前事务被唤醒

参考

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hehuyi_In

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值