一、 强锁与弱锁
根据兼容性表,彼此相容的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章