postgresql源码学习(十)—— 常规锁③-主锁表与进程锁表的初始化与查询

67 篇文章 52 订阅
20 篇文章 0 订阅

一、 主锁表与进程锁表的结构

       在数据库启动阶段,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章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hehuyi_In

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值