9-pg内核之锁管理器(四)常规锁

概述

常规锁是数据库中实现的锁,它和自旋锁及轻量锁不同,自旋锁和轻量锁属于系统锁,主要用于保护数据库系统中的一些关键变量,服务于数据库内核的实现;常规锁属于事务锁,主要用于协调各种不同事务对数据库对象如表、页、元组等的并发访问。

内存结构

锁模式

常规锁根据其不同的使用场景,分为了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信息,这就限制了弱锁的数量。

  • 申请弱锁

    • 判断是否为弱锁,其判断条件为:

      1. 锁类型是表锁,锁模式<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函数进行死锁检测。

【参考】

  1. 《PostgreSQL数据库内核分析》
  2. 《Postgresql技术内幕-事务处理深度探索》
  3. 《PostgreSQL指南:内幕探索》
  4. pg14源码
相关推荐
一个程序员_zhangzhen24 分钟前
sqlserver新建用户并分配对视图的只读权限
数据库·sqlserver
zfj32126 分钟前
学技术学英文:代码中的锁:悲观锁和乐观锁
数据库·乐观锁··悲观锁·竞态条件
吴冰_hogan28 分钟前
MySQL InnoDB 存储引擎 Redo Log(重做日志)详解
数据库·oracle
nbsaas-boot44 分钟前
探索 JSON 数据在关系型数据库中的应用:MySQL 与 SQL Server 的对比
数据库·mysql·json
cmdch20171 小时前
Mybatis加密解密查询操作(sql前),where要传入加密后的字段时遇到的问题
数据库·sql·mybatis
程序员学习随笔1 小时前
PostgreSQL技术内幕21:SysLogger日志收集器的工作原理
数据库·postgresql
Sun_12_21 小时前
SQL注入(SQL lnjection Base)21
网络·数据库
秦时明月之君临天下1 小时前
PostgreSQL标识符长度限制不能超过63字节
数据库·postgresql
woshilys1 小时前
sql server 备份恢复
数据库·sqlserver
CodeCraft Studio1 小时前
【实用技能】如何在 SQL Server 中处理 Null 或空值?
数据库·oracle·sqlserver