概念
pg数据库中,存在有大量的共享内存,进程并发访问这些共享内存的时候,就需要加锁保护,所以提供了轻量锁来提供对共享内存访问的保护。
早期版本的轻量锁是由自旋锁实现的,但是,对于频繁以共享模式获取的锁,自旋锁的开销过高,特别是在尝试获取实际上为空闲的共享锁时,会出现不必要的自旋等待。当前版本做了优化,针对未被独占锁定的锁提供无等待的共享锁获取,并利用原子操作来获取和释放锁。
轻量锁提供两种锁模式:共享模式和排他模式,其相容性矩阵如下:
锁模式 | LW_SHARED | LW_EXCLUSIVE |
---|---|---|
LW_SHARED | √ | × |
LW_EXCLUSIVE | × | × |
对于数据库的开发者而言,轻量锁有两种使用方法:统一保存的Individual LWLocks和Builtin Tranches |
Individual LWLocks
Individual LWLocks被保存在MainLWLockArray数组中,该数组的前48个都是Individual LWLocks,每个LWLock都是全局唯一的,每种Individual LWLocks都有自己固定要保护的对象,它的使用方式是:
LWLockAcquire
xxxx
LWLockRelease
Individual LWLocks保存了48种锁,保存在文件lwlocknames.txt中
#define ShmemIndexLock (&MainLWLockArray[1].lock)
#define OidGenLock (&MainLWLockArray[2].lock)
#define XidGenLock (&MainLWLockArray[3].lock)
#define ProcArrayLock (&MainLWLockArray[4].lock)
#define SInvalReadLock (&MainLWLockArray[5].lock)
#define SInvalWriteLock (&MainLWLockArray[6].lock)
#define WALBufMappingLock (&MainLWLockArray[7].lock)
#define WALWriteLock (&MainLWLockArray[8].lock)
#define ControlFileLock (&MainLWLockArray[9].lock)
#define XactSLRULock (&MainLWLockArray[11].lock)
#define SubtransSLRULock (&MainLWLockArray[12].lock)
#define MultiXactGenLock (&MainLWLockArray[13].lock)
#define MultiXactOffsetSLRULock (&MainLWLockArray[14].lock)
#define MultiXactMemberSLRULock (&MainLWLockArray[15].lock)
#define RelCacheInitLock (&MainLWLockArray[16].lock)
#define CheckpointerCommLock (&MainLWLockArray[17].lock)
#define TwoPhaseStateLock (&MainLWLockArray[18].lock)
#define TablespaceCreateLock (&MainLWLockArray[19].lock)
#define BtreeVacuumLock (&MainLWLockArray[20].lock)
#define AddinShmemInitLock (&MainLWLockArray[21].lock)
#define AutovacuumLock (&MainLWLockArray[22].lock)
#define AutovacuumScheduleLock (&MainLWLockArray[23].lock)
#define SyncScanLock (&MainLWLockArray[24].lock)
#define RelationMappingLock (&MainLWLockArray[25].lock)
#define NotifySLRULock (&MainLWLockArray[26].lock)
#define NotifyQueueLock (&MainLWLockArray[27].lock)
#define SerializableXactHashLock (&MainLWLockArray[28].lock)
#define SerializableFinishedListLock (&MainLWLockArray[29].lock)
#define SerializablePredicateListLock (&MainLWLockArray[30].lock)
#define SerialSLRULock (&MainLWLockArray[31].lock)
#define SyncRepLock (&MainLWLockArray[32].lock)
#define BackgroundWorkerLock (&MainLWLockArray[33].lock)
#define DynamicSharedMemoryControlLock (&MainLWLockArray[34].lock)
#define AutoFileLock (&MainLWLockArray[35].lock)
#define ReplicationSlotAllocationLock (&MainLWLockArray[36].lock)
#define ReplicationSlotControlLock (&MainLWLockArray[37].lock)
#define CommitTsSLRULock (&MainLWLockArray[38].lock)
#define CommitTsLock (&MainLWLockArray[39].lock)
#define ReplicationOriginLock (&MainLWLockArray[40].lock)
#define MultiXactTruncationLock (&MainLWLockArray[41].lock)
#define OldSnapshotTimeMapLock (&MainLWLockArray[42].lock)
#define LogicalRepWorkerLock (&MainLWLockArray[43].lock)
#define XactTruncationLock (&MainLWLockArray[44].lock)
#define WrapLimitsVacuumLock (&MainLWLockArray[46].lock)
#define NotifyQueueTailLock (&MainLWLockArray[47].lock)
#define NUM_INDIVIDUAL_LWLOCKS 48
Builtin Tranche
每个Builtin Tranche可能对应多个LWLock,它代表的是一组LWLocks,这组LWLocks虽然各自封各自的内容,但是他们的功能是相同的。
Builtin Tranche包含的类型如下:
typedef enum BuiltinTrancheIds
{
LWTRANCHE_XACT_BUFFER = NUM_INDIVIDUAL_LWLOCKS,
LWTRANCHE_COMMITTS_BUFFER,
LWTRANCHE_SUBTRANS_BUFFER,
LWTRANCHE_MULTIXACTOFFSET_BUFFER,
LWTRANCHE_MULTIXACTMEMBER_BUFFER,
LWTRANCHE_NOTIFY_BUFFER,
LWTRANCHE_SERIAL_BUFFER,
LWTRANCHE_WAL_INSERT,
LWTRANCHE_BUFFER_CONTENT,
LWTRANCHE_REPLICATION_ORIGIN_STATE,
LWTRANCHE_REPLICATION_SLOT_IO,
LWTRANCHE_LOCK_FASTPATH,
LWTRANCHE_BUFFER_MAPPING,
LWTRANCHE_LOCK_MANAGER,
LWTRANCHE_PREDICATE_LOCK_MANAGER,
LWTRANCHE_PARALLEL_HASH_JOIN,
LWTRANCHE_PARALLEL_QUERY_DSA,
LWTRANCHE_PER_SESSION_DSA,
LWTRANCHE_PER_SESSION_RECORD_TYPE,
LWTRANCHE_PER_SESSION_RECORD_TYPMOD,
LWTRANCHE_SHARED_TUPLESTORE,
LWTRANCHE_SHARED_TIDBITMAP,
LWTRANCHE_PARALLEL_APPEND,
LWTRANCHE_PER_XACT_PREDICATE_LIST,
LWTRANCHE_FIRST_USER_DEFINED
} BuiltinTrancheIds;
这些Builtin Tranche对应的锁一部分被保存在MainLWLockArray数组中,如LWTRANCHE_BUFFER_MAPPING,LWTRANCHE_LOCK_MANAGER,LWTRANCHE_PREDICATE_LOCK_MANAGER等。另一部分则是保存在使用他们的结构体中,如WALInsert的锁,是在WALInsertLockPadded结构体中定义,并在XLogShemInit函数中初始化。
for (i = 0; i < NUM_XLOGINSERT_LOCKS; i++)//初始化每个锁的内容
{
LWLockInitialize(&WALInsertLocks[i].l.lock, LWTRANCHE_WAL_INSERT);
WALInsertLocks[i].l.insertingAt = InvalidXLogRecPtr;
WALInsertLocks[i].l.lastImportantAt = InvalidXLogRecPtr;
}
无论是Builtin Tranche还是Individual LWLocks,他们都被保存在共享内存中,只是保存的位置和方式略有不同。
Extension中使用轻量锁
提供了两种方法
-
通过RequestNamedLWLockTranche和GetNamedLWLockTranche函数实现。
-
通过LWLockNewTrancheId函数获得新的Tranche ID,然后将Tranche ID和Tranche Name通过LWLockRegisterTranche建立联系,然后由LWLockInitialize来初始化轻量锁。
结构
LWLock
typedef struct LWLock
{
uint16 tranche; /* 唯一标识符*/
pg_atomic_uint32 state; /* 原子变量,用来保存轻量锁的状态 */
proclist_head waiters; /* 等待队列 */
#ifdef LOCK_DEBUG
pg_atomic_uint32 nwaiters; /* 等待者数量*/
struct PGPROC *owner; /* 锁的owner*/
#endif
} LWLock;
原子变量state是一个32位的值,用来记录当前的锁的状态。其中,
- 低24位用来作为共享锁的计数区,因为共享锁之间并不冲突,所以可以同时持有,一把轻量锁最多有2^24个持锁者
- 第24位是标记位,用来标记是否存在排他锁,因为排它锁与其他锁时互斥的,所以1位即可。
- 第28位用来标记是否有人持锁。
- 第29位用来标记是否存在重复release的情况
- 第30位用来标记是否有等待者。
![[Pasted image 20240511102658.png]]
#define LW_FLAG_HAS_WAITERS ((uint32) 1 << 30)//轻量锁是否有等待者
#define LW_FLAG_RELEASE_OK ((uint32) 1 << 29)//轻量锁是否重复释放
#define LW_FLAG_LOCKED ((uint32) 1 << 28)//是否有人持有该轻量锁
#define LW_VAL_EXCLUSIVE ((uint32) 1 << 24)//是否有人持有排他锁
#define LW_VAL_SHARED 1
#define LW_LOCK_MASK ((uint32) ((1 << 25)-1))//持锁情况掩码
/* Must be greater than MAX_BACKENDS - which is 2^23-1, so we're fine. */
#define LW_SHARED_MASK ((uint32) ((1 << 24)-1))//共享锁掩码,2^23-1
MainLWLockArray
//128
#define LWLOCK_PADDED_SIZE PG_CACHE_LINE_SIZE
//填充一整个缓存线
typedef union LWLockPadded
{
LWLock lock;
char pad[LWLOCK_PADDED_SIZE];
} LWLockPadded;
extern PGDLLIMPORT LWLockPadded *MainLWLockArray;
NamedLWLockTranche
typedef struct NamedLWLockTranche
{
int trancheId;
char *trancheName;
} NamedLWLockTranche;
函数
CreateLWLocks
在共享内存中分配空间,其结构大致如下:
InitializeLWLocks
分配空间后,根据轻量锁的类型初始化轻量锁。
- 初始化NUM_INDIVIDUAL_LWLOCKS
- 初始化BUFFER_MAPPING_LWLOCK_OFFSET,Buffer中用的锁
- 初始化LWTRANCHE_LOCK_MANAGER,lmgr中常用的锁
- 初始化LWTRANCHE_PREDICATE_LOCK_MANAGER,predicate lmgrs的轻量锁
- 初始化NamedLWLockTrancheArray,Extension自定义的轻量锁
LWLockAcquire
申请一把特定模式的轻量锁
- while循环中尝试获取轻量锁,如果获取成功,退出while循环直接调到最后一步;如果没有获取成功,继续下一步
mustwait = LWLockAttemptLock(lock, mode);//尝试获取锁
if (!mustwait)//获取到锁
{
LOG_LWDEBUG("LWLockAcquire", lock, "immediately acquired lock");
break; /* got the lock */
}
- 将自己添加到等待队列中,但是添加后并不会立即睡眠,因为在加入等待队列的过程中,锁可能已经被释放。
LWLockQueueSelf(lock, mode);//把我们加到等待队列中
- 再次尝试获取轻量锁,这次如果获取成功,则将自己从等待队列中去掉,然后退出while循环直接调到最后一步。
mustwait = LWLockAttemptLock(lock, mode); //再次尝试获取一次锁
if (!mustwait)
{
LWLockDequeueSelf(lock);//获取到锁后,再把自己从等待队列中去掉
break;
}
- 到这里说明两次尝试获取锁都失败了,自己也已经添加到等待队列,现在需要睡眠了。
for (;;)
{
PGSemaphoreLock(proc->sem);//睡眠
if (!proc->lwWaiting)
break;
extraWaits++;
}
/* Retrying, allow LWLockRelease to release waiters again. */
pg_atomic_fetch_or_u32(&lock->state, LW_FLAG_RELEASE_OK);//再次尝试获取锁
- 等待自己被唤醒,被唤醒后,重复上面操作,继续尝试获取锁,知道获取到锁退出。
- 获取到锁,更新相关计数,修正进程等待信号量的计数,以解决任何已接收的唤醒事件
held_lwlocks[num_held_lwlocks].lock = lock;
held_lwlocks[num_held_lwlocks++].mode = mode;
while (extraWaits-- > 0)
PGSemaphoreUnlock(proc->sem);//把当前进程等待时额外增加的计数减去
LWLockRelease
释放轻量锁
LWLockConditionalAcquire
尝试获取一把轻量锁,并立即返回申请结果,如果获取不到也不会睡眠。
bool mustwait;
HOLD_INTERRUPTS();//禁止中断
mustwait = LWLockAttemptLock(lock, mode);//尝试申请锁
if (mustwait)
{
RESUME_INTERRUPTS();//没申请成功,释放中断
}
else//申请成功,更新计数
{
held_lwlocks[num_held_lwlocks].lock = lock;
held_lwlocks[num_held_lwlocks++].mode = mode;
}
return !mustwait;
LWLockConflictsWithVar
等待原子变量更新
LWLockWaitForVar
等待锁释放或者原子变量更新
RequestNamedLWLockTranche
在共享内存中申请额外的轻量锁,只适用于Extension中。
GetNamedLWLockTranche
根据传入的tranche_name返回对应的轻量锁的地址
【参考】
- 《PostgreSQL数据库内核分析》
- 《Postgresql技术内幕-事务处理深度探索》
- 《PostgreSQL指南:内幕探索》
- pg14源码