一、 主锁表与进程锁表的结构
在数据库启动阶段,pg通过InitLocks函数初始化保存锁对象的共享内存空间。在共享内存中,有两个“锁表”用于保存锁对象,分别是:
- 主锁表(LockMethodLockHash)
- 进程锁表(LockMethodProcLockHash)
主锁表用于保存当前数据库中所有事务的锁对象,即LOCK结构体。
typedef struct LOCK
{
/* hash key */
LOCKTAG tag; /* 锁对象的唯一ID */
/* data */
LOCKMASK grantMask; /* 该对象已经持有的锁模式 */
LOCKMASK waitMask; /* 等待队列中请求该对象的锁模式 */
SHM_QUEUE procLocks; /* 这个锁上的所有 PROCLOCK */
PROC_QUEUE waitProcs; /* 等在这个锁上的 PROCLOCK */
int requested[MAX_LOCKMODES]; /* 请求(持有+等待)该锁的会话数 */
int nRequested; /* requested数组的元素数 */
int granted[MAX_LOCKMODES]; /* 持有该锁的会话数 */
int nGranted; /* granted数组的元素数 */
} LOCK;
进程锁表保存当前进程(会话)的事务锁状态,即PROCLOCK结构体,这个结构体主要用于建立锁和会话的关系。
typedef struct PROCLOCKTAG
{
/* NB: we assume this struct contains no padding! */
LOCK *myLock; /* link to per-lockable-object information,指向每个锁会话信息 */
PGPROC *myProc; /* link to PGPROC of owning backend,指向进程PGPROC */
} PROCLOCKTAG;
typedef struct PROCLOCK
{
/* tag */
PROCLOCKTAG tag; /* proclock 的唯一ID */
/* data */
PGPROC *groupLeader; /* proc's lock group leader, or proc itself,并行执行时,并行会话的leader */
LOCKMASK holdMask; /* 当前会话在这个对象上持有的锁模式 */
LOCKMASK releaseMask; /* 记录需要释放的锁模式 */
SHM_QUEUE lockLink; /* list link in LOCK's list of proclocks,LOCK结构体的proclocks字段 */
SHM_QUEUE procLink; /* list link in PGPROC's list of proclocks,PGPROC结构体的proclocks字段 */
} PROCLOCK;
二、 InitLocks函数
来看看初始化函数InitLocks,它的用途就是初始化我们反复提到的4个锁对象——主锁表、进程锁表、fast-path对象、本地锁表。
/*
* InitLocks -- Initialize the lock manager's data structures.
*/
void
InitLocks(void)
{
HASHCTL info;
long init_table_size,
max_table_size;
bool found;
/*
* Compute init/max size to request for lock hashtables. Note these
* calculations must agree with LockShmemSize!
* 计算锁hash表的初始和最大大小,最大为max_table_size,初始大小为最大大小的一半
*/
max_table_size = NLOCKENTS();
init_table_size = max_table_size / 2;
初始化主锁表对象LOCK(LockMethodLockHash),主锁表用于保存每个锁对象信息
/*
* Allocate hash table for LOCK structs. This stores per-locked-object information.
*/
info.keysize = sizeof(LOCKTAG);
info.entrysize = sizeof(LOCK);
info.num_partitions = NUM_LOCK_PARTITIONS;
LockMethodLockHash = ShmemInitHash("LOCK hash",
init_table_size,
max_table_size,
&info,
HASH_ELEM | HASH_BLOBS | HASH_PARTITION);
/* Assume an average of 2 holders per lock,假设平均每个锁有两个holders */
max_table_size *= 2;
init_table_size *= 2;
初始化进程锁表对象PROCLOCK(LockMethodProcLockHash),进程锁表用于保存每个锁对象的每个holder(per-lock-per-holder)信息
/*
* Allocate hash table for PROCLOCK structs. This stores
* per-lock-per-holder information.
*/
info.keysize = sizeof(PROCLOCKTAG);
info.entrysize = sizeof(PROCLOCK);
info.hash = proclock_hash;
info.num_partitions = NUM_LOCK_PARTITIONS;
LockMethodProcLockHash = ShmemInitHash("PROCLOCK hash",
init_table_size,
max_table_size,
&info,
HASH_ELEM | HASH_FUNCTION | HASH_PARTITION);
初始化fast-path变量 FastPathStrongRelationLocks(注意这不是个hash表,从它的初始化语句也能看出跟另外3个是不同的),记录是否有事务已获得这个对象的“强锁”。fast-path将对弱锁的访问保存到本进程,避免频繁访问主锁表和进程锁表。
/*
* Allocate fast-path structures.
*/
FastPathStrongRelationLocks =
ShmemInitStruct("Fast Path Strong Relation Lock Data",
sizeof(FastPathStrongRelationLockData), &found);
if (!found)
SpinLockInit(&FastPathStrongRelationLocks->mutex);
初始化本地锁表对象LOCALLOCK(LockMethodLocalHash),本地锁表用于保存本地锁计数和资源属主信息,这不是个共享表。
/*
* Allocate non-shared hash table for LOCALLOCK structs. This stores lock
* counts and resource owner information.
*/
if (LockMethodLocalHash)
hash_destroy(LockMethodLocalHash);
info.keysize = sizeof(LOCALLOCKTAG);
info.entrysize = sizeof(LOCALLOCK);
LockMethodLocalHash = hash_create("LOCALLOCK hash",
16,
&info,
HASH_ELEM | HASH_BLOBS);
}
三、 SetupLockInTable函数
实际上,本地锁表和fast path的主要目的都是提升性能,它们已经可以处理大多数情况,其他情况则需要对主锁表和进程锁表进行查询,根据锁的状态决定是获得锁还是进入等待。
SetupLockInTable函数的主要作用就是在主锁表和进程锁表中查找对应的锁,如果该锁还不存在,则在主锁表和进程锁表中申请内存来保存锁并初始化锁的信息。
1. gdb测试
会话1
会话2
2. 具体代码与跟踪
函数调用栈如下:
/*
* Find or create LOCK and PROCLOCK objects as needed for a new lock
* request.
*
* Returns the PROCLOCK object, or NULL if we failed to create the objects
* for lack of shared memory.
*
* The appropriate partition lock must be held at entry, and will be
* held at exit.
*/
static PROCLOCK *
SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc,
const LOCKTAG *locktag, uint32 hashcode, LOCKMODE lockmode)
{
LOCK *lock;
PROCLOCK *proclock;
PROCLOCKTAG proclocktag;
uint32 proclock_hashcode;
bool found;
/*
* Find or create a lock with this tag.从主锁表LockMethodLockHash中查找lock对象
*/
lock = (LOCK *) hash_search_with_hash_value(LockMethodLockHash,
(const void *) locktag,
hashcode,
HASH_ENTER_NULL,
&found);
if (!lock) //按照函数注释,return NULL表示由于共享内存不足报错了,lock为空(正常找不找得到都不应该是空)
return NULL;
/*
* if it's a new lock object, initialize it,如果是新的锁,初始化锁对象,这个跟之前本地锁表类似
*/
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);
}
else //如果不是,输出已找到该锁,并做一些判断
{
LOCK_PRINT("LockAcquire: found", lock, lockmode);
Assert((lock->nRequested >= 0) && (lock->requested[lockmode] >= 0));
Assert((lock->nGranted >= 0) && (lock->granted[lockmode] >= 0));
Assert(lock->nGranted <= lock->nRequested);
}
这里因为found为false,需要初始化锁对象
/*
* Create the hash key for the proclock table. 为进程锁表创建hash key */
proclocktag.myLock = lock;
proclocktag.myProc = proc;
proclock_hashcode = ProcLockHashCode(&proclocktag, hashcode);
/*
* Find or create a proclock entry with this tag,根据lock和PGPROC信息在进程锁表LockMethodProcLockHash中查找
*/
proclock = (PROCLOCK *) hash_search_with_hash_value(LockMethodProcLockHash,
(void *) &proclocktag,
proclock_hashcode,
HASH_ENTER_NULL,
&found);
if (!proclock) // 这里一样,如果proclock为空表示由于共享内存不足报错了,需要把主锁表中的信息也删除,防止内存泄漏
{
/* Oops, not enough shmem for the proclock */
if (lock->nRequested == 0)
{
/*
* There are no other requestors of this lock, so garbage-collect
* the lock object. We *must* do this to avoid a permanent leak
* of shared memory, because there won't be anything to cause
* anyone to release the lock object later.
*/
Assert(SHMQueueEmpty(&(lock->procLocks)));
if (!hash_search_with_hash_value(LockMethodLockHash,
(void *) &(lock->tag),
hashcode,
HASH_REMOVE,
NULL))
elog(PANIC, "lock table corrupted");
}
return NULL;
}
/*
* If new, initialize the new entry,如果没找到,初始化进程锁表
*/
if (!found)
{
uint32 partition = LockHashPartition(hashcode);
proclock->groupLeader = proc->lockGroupLeader != NULL ?
proc->lockGroupLeader : proc;
proclock->holdMask = 0;
proclock->releaseMask = 0;
/* Add proclock to appropriate lists */
SHMQueueInsertBefore(&lock->procLocks, &proclock->lockLink);
SHMQueueInsertBefore(&(proc->myProcLocks[partition]),
&proclock->procLink);
PROCLOCK_PRINT("LockAcquire: new", proclock);
}
这里因为没找到proclock,需要初始化
else //否则,输出找到了,并做一些判断
{
PROCLOCK_PRINT("LockAcquire: found", proclock);
Assert((proclock->holdMask & ~lock->grantMask) == 0);
#ifdef CHECK_DEADLOCK_RISK
//死锁风险判断
{
int i;
for (i = lockMethodTable->numLockModes; i > 0; i--) //从拥有的最高级模式锁开始检查
{
if (proclock->holdMask & LOCKBIT_ON(i))
{
if (i >= (int) lockmode)//如果持锁等级>=请求等级,不会有死锁风险
break; /* safe: we have a lock >= req level */
elog(LOG, "deadlock risk: raising lock level"
" from %s to %s on object %u/%u/%u",
lockMethodTable->lockModeNames[i],
lockMethodTable->lockModeNames[lockmode],
lock->tag.locktag_field1, lock->tag.locktag_field2,
lock->tag.locktag_field3);
break;
}
}
}
#endif /* CHECK_DEADLOCK_RISK */
}
/*
* lock->nRequested and lock->requested[] count the total number of
* requests, whether granted or waiting, so increment those immediately.
* The other counts don't increment till we get the lock.
*/
lock->nRequested++; //总的锁请求模式加1
lock->requested[lockmode]++; // 对应的锁模式请求数加1
Assert((lock->nRequested > 0) && (lock->requested[lockmode] > 0));
/*
* We shouldn't already hold the desired lock; else locallock table is
* broken.
*/
if (proclock->holdMask & LOCKBIT_ON(lockmode))
elog(ERROR, "lock %s on object %u/%u/%u is already held",
lockMethodTable->lockModeNames[lockmode],
lock->tag.locktag_field1, lock->tag.locktag_field2,
lock->tag.locktag_field3);
return proclock;
}
因为found为false,直接跳到了最后,执行完成
参考
《PostgreSQL技术内幕:事务处理深度探索》第2章