概述
常规锁是数据库中实现的锁,它和自旋锁及轻量锁不同,自旋锁和轻量锁属于系统锁,主要用于保护数据库系统中的一些关键变量,服务于数据库内核的实现;常规锁属于事务锁,主要用于协调各种不同事务对数据库对象如表、页、元组等的并发访问。
内存结构
锁模式
常规锁根据其不同的使用场景,分为了8中锁模式,每种模式权限不同,这样就可以在不同的场景下通过申请不同模式的锁,从而控制锁的权限。
- AccessShareLock
访问共享锁,查询时会自动施加到被查询的表上。 - RowShareLock
行共享锁,当SQL语句中采用了select...for update 和for share语句时将使用行共享锁对表加锁。 - RowExclusiveLock
行排他锁,使用IUD语句时将使用行排它锁对表加锁,但只是一个意向锁,表明有事务对该表尽显IUD操作而已,并不会真正对行加锁,行锁另有其他的实现方法。 - ShareUpdateExclusiveLock
共享更新排它锁,使用vacuum或者create index concurrently语句时使用该锁。 - ShareLock
共享锁,使用不带concurrently选项的create index语句请求时用共享锁对表加锁。 - ShareRowExclusiveLock
共享行排它锁,类似与排它锁,但是允许行共享。任何数据库命令都不会请求这个锁,除了lock命令。如,lock table tb1 in share row exclusive mode - ExclusiveLock
排它锁,阻塞行共享和select...for update语句。数据库中不会再用户表上自动请求这个锁,但是再系统表的可能会请求这个锁。 - AccessExclusiveLock
访问排它锁,执行alter table, truncate ,reindex,cluster,drop table 以及vacuum full等SQL命令时会添加该锁,是最高级别的锁。
锁的相容性矩阵
常用锁结构
常规锁主要保存在4个位置:
- 主锁表: 在共享内存中,保存了一个锁对象的所有相关信息
- 进程锁表:在共享内存中,保存了一个锁对象与当前进程的所有相关信息
- 快速路径:在当前进程中,保存了弱锁的信息,避免频繁重复访问主锁表和进程锁表。
从上面的锁模式相容性矩阵可以看出,AccessShareLock、RowShareLock、RowExclusiveLock这三个锁模式是互不冲突的,而且常用于DML操作,因此成为弱锁,剩余5个锁则为强锁。弱锁之间互不冲突,强锁和弱锁冲突。 - 本地锁表:保存在当前进程中,对重复申请的锁进行计数,相当与本地缓存,避免重复访问主锁表和进程锁表。
所以申请锁时从上述4个位置查找的流程为:本地锁表-->快速路径-->进程锁表=主锁表,有三个全局变量控制,是哈希表
static HTAB *LockMethodLockHash;//本地锁表
static HTAB *LockMethodProcLockHash;//进程锁表
static HTAB *LockMethodLocalHash;//主锁表
结构体
锁相关的结构体主要就是上述几种类型,如下图
Lock
Lock结构体记录了主锁表中锁对象的所有相关信息,比如当前持有锁的信息,等待锁的信息,关联的进程等信息。
c
typedef enum LockTagType
{
LOCKTAG_RELATION, /* 表锁 */
LOCKTAG_RELATION_EXTEND, /* 对表进行扩展时加的锁*/
LOCKTAG_DATABASE_FROZEN_IDS, /* 数据库冻结ID时的锁 */
LOCKTAG_PAGE, /* 页锁 */
LOCKTAG_TUPLE, /* 行锁 */
LOCKTAG_TRANSACTION, /* 事务锁*/
LOCKTAG_VIRTUALTRANSACTION, /* 虚拟事务锁 */
LOCKTAG_SPECULATIVE_TOKEN, /* UPSERT语句中需要等待confirm的元组加锁*/
LOCKTAG_OBJECT, /* 非表对象锁*/
LOCKTAG_USERLOCK, /* 用户锁 */
LOCKTAG_ADVISORY /* 咨询锁*/
} LockTagType;
typedef struct LOCKTAG
{
uint32 locktag_field1; /* dboid */
uint32 locktag_field2; /* reloid */
uint32 locktag_field3; /* blocknum */
uint16 locktag_field4; /* forknum */
uint8 locktag_type; /* 锁类型,参见LockTagType类型 */
uint8 locktag_lockmethodid; /* lockmethod indicator: 锁方法,目前就两种:默认锁方法和用户锁方法 */
} LOCKTAG;
typedef struct LOCK
{
/* hash key */
LOCKTAG tag; /* 标识符,全局唯一*/
/* data */
LOCKMASK grantMask; /* 已经被持有的锁的掩码 */
LOCKMASK waitMask; /* 正在等待的锁的掩码 */
SHM_QUEUE procLocks; /* 当前锁相关联的进程锁表 */
PROC_QUEUE waitProcs; /* 正在等待当前锁的进程锁表 */
int requested[MAX_LOCKMODES]; /* 需要当前锁的会话的数量 */
int nRequested; /* 需索当前锁的所有会话数量 */
int granted[MAX_LOCKMODES]; /* 已经持有当前锁的会话数 */
int nGranted; /* 已经持有当前锁的所有会话的数量 */
} LOCK;
LocalLock
本地锁表就是锁对象在本地进程的缓存,进程第一次申请一个锁对象后会将其放到本地锁表中,后面若是需要再申请该锁,则直接从本地锁表中获取即可,不需要再去主锁表申请,能大大提升性能。
c
typedef struct LOCALLOCKTAG
{
LOCKTAG lock; /* 本地锁表的标识符*/
LOCKMODE mode; /* 表的锁模式 */
} LOCALLOCKTAG;
//本地锁表类型
typedef struct LOCALLOCK
{
/* tag */
LOCALLOCKTAG tag; /* 锁标识符: */
/* data */
uint32 hashcode; /* 锁标识符的哈希值: */
LOCK *lock; /* 锁对象:*/
PROCLOCK *proclock; /* 进程锁表对象 */
int64 nLocks; /* 锁被持有的最大次数 */
int numLockOwners; /* 相关的resourceOwner数量: */
int maxLockOwners; /* 分匹配的数组大小 */
LOCALLOCKOWNER *lockOwners; /* 动态再分配*/
bool holdsStrongLockCount; /* 是否持有强锁 */
bool lockCleared; /* 锁是否已清理 */
} LOCALLOCK;
ProcLock
PROCLOCK结构记录进程锁表所有相关信息,它是锁对象与进程连接的纽带,这样就能通过Lock锁对象查找申请该锁对应的进程信息,也能通过Proc进程对象查找其申请的锁的信息。
c
typedef struct PROCLOCKTAG
{
/* NB: we assume this struct contains no padding! */
LOCK *myLock; /* 关联的锁的信息 */
PGPROC *myProc; /* 关联的进程的信息 */
} PROCLOCKTAG;
//进程锁表,每个需求锁的信息
typedef struct PROCLOCK
{
/* tag */
PROCLOCKTAG tag; /* 进程锁表标识,全局唯一 */
/* data */
PGPROC *groupLeader; /* 每个锁组的组长信息 */
LOCKMASK holdMask; /* 当前持有锁的类型的掩码信息*/
LOCKMASK releaseMask; /* 要释放的锁类型的掩码 */
SHM_QUEUE lockLink; /* 进程锁表的锁链表*/
SHM_QUEUE procLink; /* 进程锁表的进程链表*/
} PROCLOCK;
相关函数
LockAcquireExtended
该函数的作用就是申请一把常规锁。其申请流程就是判断申请的锁的位置,然后申请,先尝试从本地锁表中申请,然后尝试从快速路径中申请,最后是从主锁表和进程锁表申请。
从本地锁表申请
本地锁表是由一个全局变量LockMethodLocalHash记录,这是一个哈希表,申请本地锁表会根据申请的锁的类型和信息从LockMethodLocalHash中进行查找,并返回查找结果。
如果找到则增加锁计数后直接返回申请结果。如果本地锁表已经没有空间保存申请的锁的LockOwner信息,则扩容一倍。
如果没有找到,初始化一个空的本地锁表结构,后面在其他位置申请到后直接保存到本地锁表中。
c
//去本地锁表查询,是一个哈希表,找到的话,返回找到的本地锁对象
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;
locallock->lockOwners = (LOCALLOCKOWNER *)
MemoryContextAlloc(TopMemoryContext,
locallock->maxLockOwners * sizeof(LOCALLOCKOWNER));
}
else//本地锁表找到了
{
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;
if (locallockp)
*locallockp = locallock;//保存找到的本地锁
if (locallock->nLocks > 0)
{
GrantLockLocal(locallock, owner);//增加锁计数
if (locallock->lockCleared) //返回结果
return LOCKACQUIRE_ALREADY_CLEAR;
else
return LOCKACQUIRE_ALREADY_HELD;
}
从快速路径申请
从快速路径申请,分为申请弱锁和强锁。
本地进程最多保存16把弱锁,这是因为本地进程PGPROC->fpRelId是一个长度为16的数组,它里面保存的是本地进程只有锁的表的OID信息,这就限制了弱锁的数量。
-
申请弱锁
-
判断是否为弱锁,其判断条件为:
- 锁类型是表锁,锁模式<4,即前三种锁,通过EligibleForRelationFastPath宏判断
c#define EligibleForRelationFastPath(locktag, mode) \
((locktag)->locktag_lockmethodid == DEFAULT_LOCKMETHOD &&
(locktag)->locktag_type == LOCKTAG_RELATION &&
(locktag)->locktag_field1 == MyDatabaseId &&
MyDatabaseId != InvalidOid &&
(mode) < ShareUpdateExclusiveLock)
```
2. 本地保存的快速路径锁数量小于16- 获取强锁的哈希码
- 判断强锁是否存在,根据全局变量FastPathStrongRelationLocks来判断,如果存在,则申请失败
- 如果强锁不存在,则申请弱锁
- 申请到弱锁,更新锁计数并返回
-
c
if (EligibleForRelationFastPath(locktag, lockmode) &&
FastPathLocalUseCount < FP_LOCK_SLOTS_PER_BACKEND)//判断是否为弱锁:最大可持有16个
{
uint32 fasthashcode = FastPathStrongLockHashPartition(hashcode);//获取强锁的哈希码
bool acquired;
LWLockAcquire(&MyProc->fpInfoLock, LW_EXCLUSIVE);
if (FastPathStrongRelationLocks->count[fasthashcode] != 0)//判断该强锁是否已存在,若已存在则申请失败
acquired = false;
else
acquired = FastPathGrantRelationLock(locktag->locktag_field2,
lockmode);//申请弱锁
LWLockRelease(&MyProc->fpInfoLock);
if (acquired)//申请到弱锁,直接返回
{
locallock->lock = NULL;
locallock->proclock = NULL;
GrantLockLocal(locallock, owner);//增加锁计数
return LOCKACQUIRE_OK;
}
}
- 申请强锁
- 判断是否为强锁
判断锁模式等信息,根据ConflictsWithRelationFastPath宏判断
- 判断是否为强锁
c
#define ConflictsWithRelationFastPath(locktag, mode) \
((locktag)->locktag_lockmethodid == DEFAULT_LOCKMETHOD && \
(locktag)->locktag_type == LOCKTAG_RELATION && \
(locktag)->locktag_field1 != InvalidOid && \
(mode) > ShareUpdateExclusiveLock)//判断是否为强锁
- 获取强锁哈希码
- 申请强锁
- 如果申请到强锁,则将其他进程保存的该锁的弱锁转移到主锁表中
c
if (ConflictsWithRelationFastPath(locktag, lockmode))
{
uint32 fasthashcode = FastPathStrongLockHashPartition(hashcode);//获取哈希码
BeginStrongLockAcquire(locallock, fasthashcode);//申请强锁
//将其他事务保存的弱锁保存到主锁表中,因为有了强锁之后,强锁与弱锁就冲突了
if (!FastPathTransferRelationLocks(lockMethodTable, locktag,
hashcode))
{
AbortStrongLockAcquire();//终止获取强锁
if (locallock->nLocks == 0)
RemoveLocalLock(locallock); //如果引用计数为0,就删除本地锁
else
return LOCKACQUIRE_NOT_AVAIL;
}
}
从主锁表申请
如果本地锁表和快速路径都没申请到锁,则需要去主锁表中申请了,调用SetupLockInTable函数申请,申请到之后,还需要进行锁冲突检测,如果没有冲突,就可以直接获取这个锁了,但是如果有锁冲突,就需要将自己放到等待队列上等待了。主要流程如下:
- 先申请主锁表的轻量锁MainLWLockArray,因为要访问共享内存,以排他模式申请
c
partitionLock = LockHashPartitionLock(hashcode);
LWLockAcquire(partitionLock, LW_EXCLUSIVE);
- 调用SetupLockInTable函数申请,该函数会先去主锁表和进程锁表中查找要申请的锁,如果没有找到,则需要申请内存来保存申请的锁的信息。
c
proclock = SetupLockInTable(lockMethodTable, MyProc, locktag,hashcode, lockmode);//去主锁表中寻找
if (!proclock)
return LOCKACQUIRE_NOT_AVAIL;
- 将申请到锁保存到本地锁表中,下一次再申请就可以直接从本地锁表中申请了
c
locallock->proclock = proclock;
lock = proclock->tag.myLock;
locallock->lock = lock;
- 检查锁是否与其他锁冲突,如果没有冲突更新锁计数
c
if (lockMethodTable->conflictTab[lockmode] & lock->waitMask)
found_conflict = true;
else
found_conflict = LockCheckConflicts(lockMethodTable, lockmode,lock, proclock);
if (!found_conflict)
{
GrantLock(lock, proclock, lockmode);
GrantLockLocal(locallock, owner);
- 如果锁冲突,但不需要等待,则删除当前记录的锁信息,直接返回获取失败,是否需要等待由传入的参数dontWait确定
c
if (dontWait)
{
AbortStrongLockAcquire();
if (proclock->holdMask == 0)
{
uint32 proclock_hashcode;
proclock_hashcode = ProcLockHashCode(&proclock->tag, hashcode);
SHMQueueDelete(&proclock->lockLink);
SHMQueueDelete(&proclock->procLink);
}
lock->nRequested--;
lock->requested[lockmode]--;
LOCK_PRINT("LockAcquire: conditional lock failed", lock, lockmode);
Assert((lock->nRequested > 0) && (lock->requested[lockmode] >= 0));
Assert(lock->nGranted <= lock->nRequested);
LWLockRelease(partitionLock);
if (locallock->nLocks == 0)
RemoveLocalLock(locallock);
if (locallockp)
*locallockp = NULL;
return LOCKACQUIRE_NOT_AVAIL;
}
- 如果锁冲突,且需要等待,调用WaitOnLock函数等待锁的释放。
c
MyProc->heldLocks = proclock->holdMask;
WaitOnLock(locallock, owner);//不能获取到锁,进入等待队列等待
- 释放MainLWLockArray锁
c
LWLockRelease(partitionLock);
SetupLockInTable
从主锁表和进程锁表中查找一个锁对象,如果找不到就创建一个新的。
- 查询主锁表,从全局变量LockMethodLockHash中查询
c
lock = (LOCK *) hash_search_with_hash_value(LockMethodLockHash,(const void *) locktag,hashcode,HASH_ENTER_NULL,&found);
- 没查到,创建新的锁对象
c
if (!found)//没有找到,创建一个新的锁对象
{
lock->grantMask = 0;
lock->waitMask = 0;
SHMQueueInit(&(lock->procLocks));
ProcQueueInit(&(lock->waitProcs));
lock->nRequested = 0;
lock->nGranted = 0;
MemSet(lock->requested, 0, sizeof(int) * MAX_LOCKMODES);
MemSet(lock->granted, 0, sizeof(int) * MAX_LOCKMODES);
LOCK_PRINT("LockAcquire: new", lock, lockmode);
- 查询进程锁表,从全局变量LockMethodProcLockHash中查询
c
proclocktag.myLock = lock;//获取进程锁表的tag信息
proclocktag.myProc = proc;
proclock_hashcode = ProcLockHashCode(&proclocktag, hashcode);
proclock = (PROCLOCK *) hash_search_with_hash_value(LockMethodProcLockHash,(void *) &proclocktag,proclock_hashcode,HASH_ENTER_NULL,&found);
- 没查到,创建新的进程锁表对象
c
if (!found)//如果没找到就是创建一个新的进程锁表
{
uint32 partition = LockHashPartition(hashcode);
proclock->groupLeader = proc->lockGroupLeader != NULL ?proc->lockGroupLeader : proc;
proclock->holdMask = 0;
proclock->releaseMask = 0;
SHMQueueInsertBefore(&lock->procLocks, &proclock->lockLink);
SHMQueueInsertBefore(&(proc->myProcLocks[partition]),
&proclock->procLink);
PROCLOCK_PRINT("LockAcquire: new", proclock);
}
LockCheckConflicts
检查当前要申请的锁是否与其他的进程持有的锁冲突,主要通过主锁表中的conflicttab的掩码值与要申请的锁的掩码进行比较得出结果。如果存在多进程的情况(即group),还需要排除掉同group的冲突锁,最终得出是否冲突的结果。
- 判断要申请的锁是否与主锁表中的其他锁冲突,没有冲突直接返回
c
if (!(conflictMask & lock->grantMask))//判断当前锁模式掩码与冲突的掩码。得出是否存在冲突
{
PROCLOCK_PRINT("LockCheckConflicts: no conflict", proclock);
return false;//都不冲突
}
- 如果存在冲突,遍历所有的锁模式,判断冲突的锁进程的数量,去掉当前进程,如果数量为0,表示为没有冲突
c
myLocks = proclock->holdMask;
for (i = 1; i <= numLockModes; i++)//遍历所有锁模式
{
if ((conflictMask & LOCKBIT_ON(i)) == 0)//指定的锁模式上不存在冲突
{
conflictsRemaining[i] = 0;
continue;
}
conflictsRemaining[i] = lock->granted[i];//第i个模式在Lock上被授予了多少次了
if (myLocks & LOCKBIT_ON(i))
--conflictsRemaining[i];//是当前进程持有的锁,不算冲突
totalConflictsRemaining += conflictsRemaining[i];//统计有多少个事务持有该锁,不包括自己
}
/* If no conflicts remain, we get the lock. */
if (totalConflictsRemaining == 0)//没有冲突的锁,那么可以直接获取该锁
{
PROCLOCK_PRINT("LockCheckConflicts: resolved (simple)", proclock);
return false;
}
- 如果数量不为0,且没有group,那么存在冲突
c
//如果有冲突的锁,且Groupleader是自己即单进程,则有冲突
if (proclock->groupLeader == MyProc && MyProc->lockGroupLeader == NULL)
{
Assert(proclock->tag.myProc == MyProc);
PROCLOCK_PRINT("LockCheckConflicts: conflicting (simple)",
proclock);
return true;
}
- 如果存在group,则遍历每个进程,减去与当前进程同一个group的进程,得到剩余的总数量,如果为0,不存在冲突,否则存在冲突
c
procLocks = &(lock->procLocks);
otherproclock = (PROCLOCK *)
SHMQueueNext(procLocks, procLocks, offsetof(PROCLOCK, lockLink));
//遍历所有其他的进程,减去与当前进程一个group的冲突的锁,减完如果为0就没有冲突,否则就有冲突
while (otherproclock != NULL)
{
if (proclock != otherproclock &&
proclock->groupLeader == otherproclock->groupLeader &&
(otherproclock->holdMask & conflictMask) != 0)//其他的进程且与当前进程在一个group中,且存在锁冲突
{
int intersectMask = otherproclock->holdMask & conflictMask;//是否冲突
for (i = 1; i <= numLockModes; i++)//遍历所有锁模式
{
if ((intersectMask & LOCKBIT_ON(i)) != 0)//冲突
{
conflictsRemaining[i]--;//因为是同组的,所以冲突的就不是冲突了,减掉
totalConflictsRemaining--;
}
}
if (totalConflictsRemaining == 0)//所有冲突的锁都符合,那么就不存在锁冲突了
{
PROCLOCK_PRINT("LockCheckConflicts: resolved (group)",proclock);
return false;
}
}
otherproclock = (PROCLOCK *)
SHMQueueNext(procLocks, &otherproclock->lockLink,
offsetof(PROCLOCK, lockLink));//遍历下一个
}
FastPathGrantRelationLock
申请快速路径的弱锁,每个进程最多能持有16个弱锁,所以预留的槽位只有16个,遍历 每个槽位,找到没被占用的槽位,然后将要申请的锁信息填入该槽位占住即可。
c
static bool
FastPathGrantRelationLock(Oid relid, LOCKMODE lockmode)
{
uint32 f;
uint32 unused_slot = FP_LOCK_SLOTS_PER_BACKEND;
/* Scan for existing entry for this relid, remembering empty slot. */
for (f = 0; f < FP_LOCK_SLOTS_PER_BACKEND; f++)//遍历每一个槽位,通16个槽位
{
if (FAST_PATH_GET_BITS(MyProc, f) == 0)//判断是否还未被占用
unused_slot = f;
else if (MyProc->fpRelId[f] == relid)//判断当前表是否已经申请过
{
Assert(!FAST_PATH_CHECK_LOCKMODE(MyProc, f, lockmode));
FAST_PATH_SET_LOCKMODE(MyProc, f, lockmode);
return true;
}
}
/* If no existing entry, use any empty slot. */
if (unused_slot < FP_LOCK_SLOTS_PER_BACKEND)
{
MyProc->fpRelId[unused_slot] = relid;//占用空闲的槽位
FAST_PATH_SET_LOCKMODE(MyProc, unused_slot, lockmode);//更新锁标记
++FastPathLocalUseCount;//更新快速路径计数
return true;
}
/* No existing entry, and no empty slot. */
return false;
}
BeginStrongLockAcquire
申请强锁,就是更新全局变量FastPathStrongRelationLocks的值,然后将本地锁表的holdsStrongLockCount设置为true
c
BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode)
{
SpinLockAcquire(&FastPathStrongRelationLocks->mutex);//修改全局变量,先申请锁
FastPathStrongRelationLocks->count[fasthashcode]++;//更新对应的强锁计数
locallock->holdsStrongLockCount = true;//标记一下,本地锁表申请的有强锁
StrongLockInProgress = locallock;//当前的锁是强锁
SpinLockRelease(&FastPathStrongRelationLocks->mutex);//释放锁
}
FastPathTransferRelationLocks
获取到强锁以后,如果其他进程的快速路径上还保存有对应的弱锁,就需要将这些弱锁挪到主锁表中,否则这些快速路径上的弱锁将因为与强锁冲突而失效。
该函数会遍历所有的进程,所有的进程保存在全局变量ProcGlobal->allProcs中,然后查找对应的弱锁,找到后挪到主锁表中,主要流程如下:
- 遍历每个进程,如果跟持锁的数据库信息不一致,直接跳过
- 遍历每个进程上的弱锁的槽位(16个),弱锁相关的表ID跟申请的不一致或对应槽位未被使用,直接跳过
- 遍历每个弱锁模式(3个),若该模式弱锁不存在,直接跳过
- 走到这里,就说明该进程的某个槽位存在弱锁且与申请的强锁冲突,去主锁表中重新创建该锁(即将该弱锁转移到主锁表中)。
- 清空该进程上相关的弱锁信息
c
FastPathTransferRelationLocks(LockMethod lockMethodTable, const LOCKTAG *locktag,
uint32 hashcode)
{
LWLock *partitionLock = LockHashPartitionLock(hashcode);
Oid relid = locktag->locktag_field2;
uint32 i;
for (i = 0; i < ProcGlobal->allProcCount; i++)//遍历所有的进程
{
PGPROC *proc = &ProcGlobal->allProcs[i];//取一个进程
uint32 f;
LWLockAcquire(&proc->fpInfoLock, LW_EXCLUSIVE);//获取该进程上的快速路径锁,锁住后禁止其他进程访问该进程相关的快速路径的信息
if (proc->databaseId != locktag->locktag_field1)//如果不然会同一个数据库,可以直接跳过,因为不相关
{
LWLockRelease(&proc->fpInfoLock);
continue;
}
for (f = 0; f < FP_LOCK_SLOTS_PER_BACKEND; f++)//遍历进程上的所有弱锁槽位
{
uint32 lockmode;
/* Look for an allocated slot matching the given relid. */
if (relid != proc->fpRelId[f] || FAST_PATH_GET_BITS(proc, f) == 0)//表ID或持锁信息不符合要求,跳过
continue;
/* Find or create lock object. */
LWLockAcquire(partitionLock, LW_EXCLUSIVE);//主锁表加锁
for (lockmode = FAST_PATH_LOCKNUMBER_OFFSET;
lockmode < FAST_PATH_LOCKNUMBER_OFFSET + FAST_PATH_BITS_PER_SLOT;
++lockmode)//遍历每种弱锁模式
{
PROCLOCK *proclock;
if (!FAST_PATH_CHECK_LOCKMODE(proc, f, lockmode))//不存在弱锁,跳过
continue;
proclock = SetupLockInTable(lockMethodTable, proc, locktag,
hashcode, lockmode);//在主锁表中创建或申请对应的锁
if (!proclock)
{
LWLockRelease(partitionLock);
LWLockRelease(&proc->fpInfoLock);
return false;
}
GrantLock(proclock->tag.myLock, proclock, lockmode);//更新锁计数
FAST_PATH_CLEAR_LOCKMODE(proc, f, lockmode);//清空该进程上的弱锁计数
}
LWLockRelease(partitionLock);
/* No need to examine remaining slots. */
break;
}
LWLockRelease(&proc->fpInfoLock);
}
return true;
}
WaitOnLock
当前进程睡眠并等待其期望的锁释放,调用ProcSleep函数睡眠,ProcSleep函数中又会调用CheckDeadLock函数进行死锁检测。
【参考】
- 《PostgreSQL数据库内核分析》
- 《Postgresql技术内幕-事务处理深度探索》
- 《PostgreSQL指南:内幕探索》
- pg14源码