相关:
《Postgresql源码(40)Latch的原理分析和应用场景》
《Postgresql源码(67)LWLock锁的内存结构与初始化》
《Postgresql源码(68)virtualxid锁的原理和应用场景》
《Postgresql源码(69)常规锁简单分析》
数据结构需要注意的是:
加锁过程需要注意的是:
关于第六点举例,PG经典的锁排队场景的成因(具体看3.5)
关于lockmode和locktype:
常规锁相容性复习:https://www.postgresql.org/docs/14/explicit-locking.html
共享内存中维护两个核心哈希表:
(key: LOCKTAG)->(value:LOCK)
(key: PROCLOCKTAG)->(value:PROCLOCK)
InitLocks
...
static HTAB *LockMethodLockHash;
LockMethodLockHash = ShmemInitHash("LOCK hash",
init_table_size,
max_table_size,
&info,
HASH_ELEM | HASH_BLOBS | HASH_PARTITION);
static HTAB *LockMethodProcLockHash;
LockMethodProcLockHash = ShmemInitHash("PROCLOCK hash",
init_table_size,
max_table_size,
&info,
HASH_ELEM | HASH_FUNCTION | HASH_PARTITION);
关键数据结构:
logtype
locktag_lockmethodid
目前只有两套,2是专门给咨询锁使用的,其他都是1。
/* These identify the known lock methods */
#define DEFAULT_LOCKMETHOD 1
#define USER_LOCKMETHOD 2
数据库最常发生的增删改查正常都需要去主锁表中申请常规锁,但是DML操作其实只需要弱锁,且弱锁之间是相容的;所以PG在1-3级锁上做了一层优化:如果事务对某个对象申请弱锁,且对象上没有别人申请的强锁,则可以在会话本地记录弱锁,不走主锁表,不写共享内存。
这里有两个条件:
对象上有没有别人申请过强锁这个信息,记录到下面共享内存结构中的count中,如果有加过强锁,对应位置的计数加1。注意这个数据结构是放在共享内存中的。但是访问他的代价,比每次都去主锁表中加锁要低很多。
共享内存结构:
lock.c
typedef struct
{
slock_t mutex;
uint32 count[FAST_PATH_STRONG_LOCK_HASH_PARTITIONS]; // tag算hash值后%1024到这里1024个位置,是强锁就+1
} FastPathStrongRelationLockData;
static volatile FastPathStrongRelationLockData *FastPathStrongRelationLocks;
初始化:
InitLocks
...
FastPathStrongRelationLocks =
ShmemInitStruct("Fast Path Strong Relation Lock Data",
sizeof(FastPathStrongRelationLockData), &found);
fastpath锁信息记录位置:
PGPROC
{
...
// fastpath使用
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]; // 最多能存16个oid
// 下面是VXID专用
bool fpVXIDLock; /* are we holding a fast-path VXID lock? */
LocalTransactionId fpLocalTransactionId; /* lxid for fast-path VXID
* lock */
...
}
关于fpLockBits、fpLockBits下面有具体介绍。
本地内存中维护本地锁表:LockMethodLockHash
在事务内第一次申请锁需要检查冲突,拿到锁后,如果同事务内继续申请这把锁,就可以直接获取到了。因为事务内的锁是在事务结束时统一释放的。
在申请成功后,将锁信息存到下面本地锁表结构中,供后面使用:
typedef struct LOCALLOCKTAG
{
LOCKTAG lock; /* identifies the lockable object */
LOCKMODE mode; /* lock mode for this table entry */
} LOCALLOCKTAG;
typedef struct LOCALLOCK
{
/* tag */
LOCALLOCKTAG tag; /* unique identifier of locallock entry */
/* data */
uint32 hashcode; /* copy of LOCKTAG's hash value */
LOCK *lock; // 锁
PROCLOCK *proclock; // 锁进程关系
int64 nLocks; // 本地多少次持有这个锁
int numLockOwners; // ResourceOwners的数量
int maxLockOwners; // 保存ResourceOwners的数组长度
LOCALLOCKOWNER *lockOwners; /* dynamically resizable array */
bool holdsStrongLockCount; // 是否持有强锁
bool lockCleared; /* we read all sinval msgs for lock */
} LOCALLOCK;
初始化
InitLocks
...
LockMethodLocalHash = hash_create("LOCALLOCK hash",
16,
&info,
HASH_ELEM | HASH_BLOBS);
API
LockAcquireResult
LockAcquire(const LOCKTAG *locktag, :锁tag
LOCKMODE lockmode, :申请锁模式
bool sessionLock, :会话锁 或 事务锁
bool dontWait) :是否等待
{
return LockAcquireExtended(locktag, lockmode, sessionLock, dontWait,
true, NULL);
}
LockAcquireExtended
LockAcquireResult
LockAcquireExtended(const LOCKTAG *locktag,
LOCKMODE lockmode,
bool sessionLock,
bool dontWait,
bool reportMemoryError,
LOCALLOCK **locallockp)
{
...
MemSet(&localtag, 0, sizeof(localtag)); /* must clear padding */
localtag.lock = *locktag;
localtag.mode = lockmode;
locallock = (LOCALLOCK *) hash_search(LockMethodLocalHash,
(void *) &localtag,
HASH_ENTER, &found);
if (!found)
{
没查到构造一条放入本地锁表
locallock->lock = NULL;
locallock->proclock = NULL;
locallock->hashcode = LockTagHashCode(&(localtag.lock));
locallock->nLocks = 0; // 没人持锁
locallock->holdsStrongLockCount = false; // 没有强锁
locallock->lockCleared = false;
locallock->numLockOwners = 0;
locallock->maxLockOwners = 8;
locallock->lockOwners = NULL; /* in case next line fails */
locallock->lockOwners = (LOCALLOCKOWNER *)
MemoryContextAlloc(TopMemoryContext,
locallock->maxLockOwners * sizeof(LOCALLOCKOWNER));
}
else
{
查到了,如果ResourcesOwner数组不够了,扩展数组
if (locallock->numLockOwners >= locallock->maxLockOwners)
{
int newsize = locallock->maxLockOwners * 2;
locallock->lockOwners = (LOCALLOCKOWNER *)
repalloc(locallock->lockOwners,
newsize * sizeof(LOCALLOCKOWNER));
locallock->maxLockOwners = newsize;
}
}
hashcode = locallock->hashcode;
如果locallock->nLocks,说明这个锁之前已经拿过了,就不用再走下面逻辑了。
由GrantLockLocal负责更新本地锁的内容。
1:locallock->nLocks++。
2:更新ResourceOwner,增加锁计数。
if (locallock->nLocks > 0)
{
GrantLockLocal(locallock, owner);
if (locallock->lockCleared)
return LOCKACQUIRE_ALREADY_CLEAR;
else
return LOCKACQUIRE_ALREADY_HELD;
}
进入条件一:EligibleForRelationFastPath
locktag_lockmethodid == DEFAULT_LOCKMETHOD
locktag_type == LOCKTAG_RELATION
locktag_field1 == MyDatabaseId
(mode) < ShareUpdateExclusiveLock
进入条件二:
FastPathLocalUseCount < FP_LOCK_SLOTS_PER_BACKEND
/*
#define EligibleForRelationFastPath(locktag, mode) \
((locktag)->locktag_lockmethodid == DEFAULT_LOCKMETHOD && \
(locktag)->locktag_type == LOCKTAG_RELATION && \
(locktag)->locktag_field1 == MyDatabaseId && \
MyDatabaseId != InvalidOid && \
(mode) < ShareUpdateExclusiveLock)
*/
if (EligibleForRelationFastPath(locktag, lockmode) &&
FastPathLocalUseCount < FP_LOCK_SLOTS_PER_BACKEND)
{
用tag算的hashcode在%1024,落到1024数组FastPathStrongRelationLocks->count
的某个位置上。
uint32 fasthashcode = FastPathStrongLockHashPartition(hashcode);
bool acquired;
LWLockAcquire(&MyProc->fpInfoLock, LW_EXCLUSIVE);
查共享内存fastpath锁表FastPathStrongRelationLocks,检查是否有强锁。
if (FastPathStrongRelationLocks->count[fasthashcode] != 0)
acquired = false;
else
没发现强锁,可以用fastpath加锁,下面要判断是否有位置能加fastpath锁。
(注意这里只把locktag_field2传进去了,表锁的tag组成是:
[dbid, redid](SET_LOCKTAG_RELATION)
,所以这里只把表的OID传进去了)
FastPathGrantRelationLock函数:
Oid fpRelId[16];
来保存OID。uint64 fpLockBits
当做位图来记录锁级别。FastPathGrantRelationLock逻辑:
static bool
FastPathGrantRelationLock(Oid relid, LOCKMODE lockmode)
{
uint32 f;
uint32 unused_slot = FP_LOCK_SLOTS_PER_BACKEND;
for (f = 0; f < FP_LOCK_SLOTS_PER_BACKEND; f++)
{
if (FAST_PATH_GET_BITS(MyProc, f) == 0)
unused_slot = f;
else if (MyProc->fpRelId[f] == relid)
{
FAST_PATH_SET_LOCKMODE(MyProc, f, lockmode);
return true;
}
}
if (unused_slot < FP_LOCK_SLOTS_PER_BACKEND)
{
MyProc->fpRelId[unused_slot] = relid;
FAST_PATH_SET_LOCKMODE(MyProc, unused_slot, lockmode);
++FastPathLocalUseCount;
return true;
}
return false;
}
回到LockAcquireExtended,如果拿到了fastpath,就可以直接返回了。
返回前要把locallock的lock和proclock置空,但是本地锁表还是有对应的项在的!区别就是locallock->lock字段是不是空的。
acquired = FastPathGrantRelationLock(locktag->locktag_field2,
lockmode);
LWLockRelease(&MyProc->fpInfoLock);
if (acquired)
{
locallock->lock = NULL;
locallock->proclock = NULL;
GrantLockLocal(locallock, owner);
return LOCKACQUIRE_OK;
}
}
ConflictsWithRelationFastPath检查:是强锁!
locktag_lockmethodid == DEFAULT_LOCKMETHOD
locktag_type == LOCKTAG_RELATION
(mode) > ShareUpdateExclusiveLock
if (ConflictsWithRelationFastPath(locktag, lockmode))
{
uint32 fasthashcode = FastPathStrongLockHashPartition(hashcode);
开始处理强锁:BeginStrongLockAcquire
FastPathStrongRelationLocks->count[fasthashcode]++;
locallock->holdsStrongLockCount = true
StrongLockInProgress = locallock
fastpath锁转换到主锁表:FastPathTransferRelationLocks
BeginStrongLockAcquire(locallock, fasthashcode);
if (!FastPathTransferRelationLocks(lockMethodTable, locktag,
hashcode))
{
// 加锁失败处理,共享内存不足。
}
}
本地锁表没有、fastpath不能用(满了或者是强锁),到这里开始操作主锁表。
SetupLockInTable构造 LOCK 和 PROCLOCK结构,完成主锁表、进程锁表插入
partitionLock = LockHashPartitionLock(hashcode);
LWLockAcquire(partitionLock, LW_EXCLUSIVE);
// 构造 LOCK 和 PROCLOCK结构,完成主锁表、进程锁表插入
proclock = SetupLockInTable(lockMethodTable, MyProc, locktag,
hashcode, lockmode);
...
locallock->proclock = proclock;
lock = proclock->tag.myLock;
locallock->lock = lock;
lockMethodTable->conflictTab[lockmode]
获得是与申请锁级别冲突的级别,例如申请1级锁:
lockmode = 1
lockMethodTable->conflictTab[lockmode] = 100000000
lockMethodTable->conflictTab[lockmode] & lock->waitMask
表示如果当前申请的锁级别(比如我需要1级锁),和别人等在这个锁上的锁级别(别人需要8级锁,在等)不相容,那么当前申请的锁必须进入等待状态(我需要的1级锁必须等锁)。
所以就造成了PG经典的锁排队场景:
if (lockMethodTable->conflictTab[lockmode] & lock->waitMask)
found_conflict = true;
else
found_conflict = LockCheckConflicts(lockMethodTable, lockmode,
lock, proclock);
if (!found_conflict)
{
/* No conflict with held or previously requested locks */
GrantLock(lock, proclock, lockmode);
GrantLockLocal(locallock, owner);
}
else
{
...
WaitOnLock(locallock, owner);
...
}
/*
* Lock state is fully up-to-date now; if we error out after this, no
* special error cleanup is required.
*/
FinishStrongLockAcquire();
LWLockRelease(partitionLock);
return LOCKACQUIRE_OK;
}