postgresql源码学习(九)—— 常规锁②-强弱锁与Fast Path

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

一、 强锁与弱锁

         根据兼容性表,彼此相容的3个锁(1-3级,AccessShareLock、RowShareLock、RowExclusiveLock)是弱锁,4级锁ShareUpdateExclusiveLock比较特殊,既不是强锁也不是弱锁,5-8级的4个则是强锁。

       弱锁只保存在当前会话,从而避免频繁访问共享内存的主锁表,提高数据库的性能。虽然判断是否有强锁也需要访问共享内存中的FastPathStrongRelationLocks,但这种访问粒度比较小。

二、 Fast Path检查

        按照常规锁获取步骤,如果本地锁表中没有找到这个锁对象和锁模式,就需要进到第二步Fast Path检查。

1. 检查条件

         Fast Path检查分两部分:弱锁部分与强锁部分,pg分别使用两个宏来判断(lock.c)。

// 弱锁需要满足4个条件:
// 1. 默认lock method;2. 对表加锁;3. 当前db;4. 锁模式小于ShareUpdateExclusiveLock(4级锁)
#define EligibleForRelationFastPath(locktag, mode) \
	((locktag)->locktag_lockmethodid == DEFAULT_LOCKMETHOD && \
	(locktag)->locktag_type == LOCKTAG_RELATION && \
	(locktag)->locktag_field1 == MyDatabaseId && \
	MyDatabaseId != InvalidOid && \
	(mode) < ShareUpdateExclusiveLock)
// 强锁也需要满足4个条件:
// 只有第4个为锁模式大于ShareUpdateExclusiveLock(4级锁),其余3个与弱锁相同
#define ConflictsWithRelationFastPath(locktag, mode) \
	((locktag)->locktag_lockmethodid == DEFAULT_LOCKMETHOD && \
	(locktag)->locktag_type == LOCKTAG_RELATION && \
	(locktag)->locktag_field1 != InvalidOid && \
	(mode) > ShareUpdateExclusiveLock)

注意判断的时候都没有mode = ShareUpdateExclusiveLock,因为它既不是强锁也不是弱锁。

2. 弱锁申请时

       一个弱锁能够直接获得而不经过主锁表,需要判断两个条件:

  • 共享内存中是否设置了强锁标记

       如果共享内存中没有设置强锁标记,说明当前没有其他事务获得过这个锁对象的强锁模式。因此事务在这个对象间不会有冲突,此时可以考虑获得这个弱锁。

  • 当前进程是否还有空间可以保存弱锁

       如果弱锁保存在本事务的fast path中,实际上它是保存在PGPROC中的(以下代码在proc.h)。

#define		FP_LOCK_SLOTS_PER_BACKEND 16

	/* Lock manager data, recording fast-path locks taken by this backend. */
	LWLock		fpInfoLock;		/* protects per-backend fast-path state */
	uint64		fpLockBits;		/* lock modes held for each fast-path slot */
	Oid			fpRelId[FP_LOCK_SLOTS_PER_BACKEND]; /* slots for rel oids */

       PGPROC->fpRelId是一个长度为16的数组,里面保存的是表的oid,因此最多保存16个弱锁。

       PGPROC->fpLockBits保存的是一个位图,目前64位中(uint64)只用了48位。每3位组成一个槽(因为共有3种弱锁),每个槽都与PGPROC->fpRelId数组中的表对应(所以3*16=48)。

/* Macros for manipulating proc->fpLockBits,3种弱锁所以每3位组成一个槽*/
#define FAST_PATH_BITS_PER_SLOT		3
#define FAST_PATH_LOCKNUMBER_OFFSET		1
//形式为111的掩码
#define FAST_PATH_MASK					((1 << FAST_PATH_BITS_PER_SLOT) - 1)
//得到第n个槽中已有的锁模式
#define FAST_PATH_GET_BITS(proc, n) \
	(((proc)->fpLockBits >> (FAST_PATH_BITS_PER_SLOT * n)) & FAST_PATH_MASK)

       所以当一个事务要对弱锁使用fast path时,就尝试在PGPROC->fpLockBits中记录当前的锁模式,事务就能够获得锁了。

       当然,如果记录的弱锁数超过了16,那也需要去主锁表中检查。

3. 强锁申请时

如果要申请的强锁模式,需要做的事如下:

  • 设置强锁标记,即 FastPathStrongRelationLocks->count(也是一个数组)的引用计数加1
  • 把其他事务保存的对应弱锁转移到主锁表中,方便死锁检测(因为有强锁之后就会有等待)

源码实现部分较长,为了结构清晰一些,我们单独放下面。

三、 强锁申请时的fast path操作

1. 设置强锁标记

  • FastPathStrongRelationLocks的定义
typedef struct
{
	slock_t		mutex;
	uint32		count[FAST_PATH_STRONG_LOCK_HASH_PARTITIONS]; //数组最大长度1024
} FastPathStrongRelationLockData;

static volatile FastPathStrongRelationLockData *FastPathStrongRelationLocks;
  • 强锁获取函数

       核心是FastPathStrongRelationLocks->count[fasthashcode]++; 以及 locallock->holdsStrongLockCount = true;

/*
 * BeginStrongLockAcquire - inhibit use of fastpath for a given LOCALLOCK,
 * and arrange for error cleanup if it fails
 */
static void
BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode)
{
	Assert(StrongLockInProgress == NULL);
	Assert(locallock->holdsStrongLockCount == false);

	/*
	 * Adding to a memory location is not atomic, so we take a spinlock to
	 * ensure we don't collide with someone else trying to bump the count at
	 * the same time.
	 *
	 * XXX: It might be worth considering using an atomic fetch-and-add
	 * instruction here, on architectures where that is supported.
	 */

	SpinLockAcquire(&FastPathStrongRelationLocks->mutex);
	FastPathStrongRelationLocks->count[fasthashcode]++;  //注意这里,强锁引用计数加1
	locallock->holdsStrongLockCount = true;
	StrongLockInProgress = locallock;
	SpinLockRelease(&FastPathStrongRelationLocks->mutex);
}

2. 弱锁转移到主锁表中

       把其他事务保存的对应弱锁转移到主锁表中。整体就是3个大循环:检查每个进程中的、每个表的、3种弱锁模式是否设置过。

/*
 * FastPathTransferRelationLocks
 *		Transfer locks matching the given lock tag from per-backend fast-path
 *		arrays to the shared hash table.
 *
 * Returns true if successful, false if ran out of shared memory.
 */
static bool
FastPathTransferRelationLocks(LockMethod lockMethodTable, const LOCKTAG *locktag,
							  uint32 hashcode)
{
	LWLock	   *partitionLock = LockHashPartitionLock(hashcode);
	Oid			relid = locktag->locktag_field2;
	uint32		i;

/*
	 * Every PGPROC that can potentially hold a fast-path lock is present in
	 * ProcGlobal->allProcs.  Prepared transactions are not, but any
	 * outstanding fast-path locks held by prepared transactions are
	 * transferred to the main lock table.
* 每个PGPROC都有可能持有fast-path锁(在ProcGlobal->allProcs数组中),因此需要循环遍历所有PGPROC。所有prepared阶段之后的事务需要将fast-path locks转移到主锁表。
	 */
	for (i = 0; i < ProcGlobal->allProcCount; i++)
	{
		PGPROC	   *proc = &ProcGlobal->allProcs[i];
		uint32		f;

		LWLockAcquire(&proc->fpInfoLock, LW_EXCLUSIVE);

/*
		 * If the target backend isn't referencing the same database as the
		 * lock, then we needn't examine the individual relation IDs at all;
		 * none of them can be relevant.
如果进程引用的库跟要求加锁的对象都不是同一个,那么不需再检查其中的表,直接跳过本次循环,换下一个检查
*/ 

		if (proc->databaseId != locktag->locktag_field1)
		{
			LWLockRelease(&proc->fpInfoLock);
			continue;
		}

		/*	
		 * 如果是同一个库,那么遍历16个针对表的槽
		 */
		for (f = 0; f < FP_LOCK_SLOTS_PER_BACKEND; f++)
		{
			uint32		lockmode;

			/* 如果不是同一个表,或者当前没有弱锁,则跳出循环,换下一个表 */
			if (relid != proc->fpRelId[f] || FAST_PATH_GET_BITS(proc, f) == 0)
				continue;

			/* 否则,继续按lockmode循环检查3类弱锁。FAST_PATH_LOCKNUMBER_OFFSET=1,FAST_PATH_BITS_PER_SLOT=3,所以其实就是for (lockmode = 1; lockmode < 1+3; ++lockmode)
 */
			LWLockAcquire(partitionLock, LW_EXCLUSIVE);
			for (lockmode = FAST_PATH_LOCKNUMBER_OFFSET;
				 lockmode < FAST_PATH_LOCKNUMBER_OFFSET + FAST_PATH_BITS_PER_SLOT;
				 ++lockmode)
			{
				PROCLOCK   *proclock;

			/* 检查3个弱锁的标记是否被设置过,如果没有,代表fast path中没出现过这个锁模式,换下一个 */
				if (!FAST_PATH_CHECK_LOCKMODE(proc, f, lockmode))
					continue;
			/* 否则,把这个锁模式挪到共享内存。SetupLockInTable函数的主要作用就是在主锁表和进程锁表中查找对应的锁,如果该锁不存在,则在主锁表和进程锁表中申请内存保存锁并初始化锁信息。我们会在下篇中重点学习这个函数。
 */
				proclock = SetupLockInTable(lockMethodTable, proc, locktag,
											hashcode, lockmode);
				if (!proclock) // Returns false if ran out of shared memory
				{
					LWLockRelease(partitionLock);
					LWLockRelease(&proc->fpInfoLock);
					return false;
				}
                 // 增加本地锁的计数 
				GrantLock(proclock->tag.myLock, proclock, lockmode);
                 // 在PGPROC中去掉对应锁模式,业务PGPROC已转移到主锁表
				FAST_PATH_CLEAR_LOCKMODE(proc, f, lockmode);
			}
			LWLockRelease(partitionLock);

			/* No need to examine remaining slots. */
			break;
		}
		LWLockRelease(&proc->fpInfoLock);
	}
	return true;
}

参考

《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、付费专栏及课程。

余额充值