postgresql regular lock常规锁申请与释放 内幕 以及fastpath快速申请优化的取舍

专栏内容
postgresql内核源码分析
手写数据库toadb
并发编程
个人主页我的主页
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.

==================================

定义

每种常规锁都需要定义几个要素,它由结构体 LockMethodData 定义;

c 复制代码
typedef struct LockMethodData
{
	int			numLockModes;
	const LOCKMASK *conflictTab;
	const char *const *lockModeNames;
	const bool *trace_flag;
} LockMethodData;

typedef const LockMethodData *LockMethod;

这几个要素分别是:

  • 锁的模式类型,也就是锁分了几种加锁方式,比如这里表锁是8种,也就是8级表锁;
  • 锁的冲突矩阵,它是一个按bit的二维表,也就是各级锁方式之间的冲突关系,比如读写互斥,读读不冲突等;
  • 锁的名字,主要是为了查找调试;

postgresql 已经定义了一种默认锁 default_lockmethod, 也可以自定义用户锁

存储

regular lock是多进程间共享的,所以存储在共享内存中。

由这几个结构组织存储:

  • LockMethodLockHash ,以locktag为hash存储使用的锁
  • LockMethodProcLockHash , 存储锁的引用关系,由lock-proc对来存储,proc是每个backend信息
  • FastPathStrongRelationLocks ,
  • LockMethodLocalHash , 存储本进程持有的所,相同锁的话,只是引用计数递增

以上hash表,在初始化时就已经分配

申请

常规锁的申请主要在接口 LockAcquire 和 LockAcquireExtended中实现。

c 复制代码
LockAcquireResult
LockAcquire(const LOCKTAG *locktag,
			LOCKMODE lockmode,
			bool sessionLock,
			bool dontWait)
{
	return LockAcquireExtended(locktag, lockmode, sessionLock, dontWait,
							   true, NULL);
}

LockAcquireResult
LockAcquireExtended(const LOCKTAG *locktag,
					LOCKMODE lockmode,
					bool sessionLock,
					bool dontWait,
					bool reportMemoryError,
					LOCALLOCK **locallockp);

可以看到最终是在 LockAcquireExtended实现 ,前者只是简单调用关系;

申请流程

下面我们来看LockAcquireExtended中的实现流程

  • 从本地锁记录中查找;如果找到则返回;如果没找到创建本地新记录;
  • 在standby模式时特殊处理;只能获取只读锁,此时需要先获取事务ID,以便锁与事务关锁;
  • fastpath 处理;

fastpath 只用在表锁模式下;当获取的锁小于4级ShareUpdateExclusiveLock时启用;

fastpath 可以记录FP_LOCK_SLOTS_PER_BACKEND 16个锁记录,根据locktag hash值取模,对应位置如果没有被占用count=0时,就进行fastpath;

通过 FastPathGrantRelationLock 进行获取锁 ;当对应bit位为0时,就直接获得锁,并将bit为置1,同时reloid数组中记录对应的reloid; 如果上次获取过4级以下的锁,那么也将直接获得;

这里有两个变量(proc)->fpLockBits和 (proc)->fpRelId[FP_LOCK_SLOTS_PER_BACKEND],前者记录锁模式,后者记录对应的reloid;前者是一个64位整型,每4bit为一组,可以记录16个锁;

  • 创建或查找锁,并创建锁proclock

当申请的是表锁且锁级别大于4时,先检查与 fastpath锁的冲突; 先在FastPathStrongRelationLocks->count[fasthashcode]对应+1,占位; 然后通过ProcGlobal来遍历所有进程中的fastpath信息;如果发现有backend已经通过fastpath占有4级以下锁,那么就创建lock和lockproc,在对应的hash中加入,这样在后面锁冲突判断时就会发现;

  • 检查锁冲突

从 LockMethodLockHash 查找锁的locktag,如果有说明已经有持有者;再从 LockMethodProcLockHash 创建持有者关系;

  • 先检查与锁等待者的冲突情况,根据lock->waitMask 来检查冲突;如果有冲突,则进行锁排队等待;
  • 再检查与已经持有的锁冲突情况,避免死锁等待; 这一步比较复杂,先检查持有锁,再检查锁组冲突;
    经过以上两步,没有冲突,则获得锁;有冲突测进行锁排队等待;
  • 锁冲突等待 ,至到有人唤醒为止

释放

在使用结束后释放锁,如果有等待者需要唤醒, 在LockRelease中进行处理。

c 复制代码
bool 
LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock);

主要流程如下:

  • 检查本地是否记录锁的持有 LockMethodLocalHash ;

本地持有,那么先对锁持有计数 -1 ,如果不为零,那就释放完成,返回true;
如果锁计数为零时,先将本地持有锁数量-1,从资源管理中取消持有记录;

  • 处理fastpath申请的情况

如果持的有为fastpath申请的锁,从fastpath信息中清除;并清除本地锁记录,返回true;

  • 当然锁完全释放,从LockMethodLockHash和LockMethodProcLockHash中删除
  • 检查当前是否持有锁,如果已经不持有,清除本地锁记录,返回false;
  • 释放锁;
    锁授予和已请求计数分别 -1; 如果当前锁模式授予为0时,将grantMask的锁模式位置0;
    检查是否有等待者,也就是看waitMask是否有冲突,如果有则需要唤醒;
    在proclock中将持有锁holdMask中将当前锁模式置为0;
  • 清理锁并唤醒等待者;
    如果当前不再持有锁,则将lockproc从hash表中删除;
    如果当前锁的请求者为0时,将lock从hash表中删除;
    如果有请求者,也就是锁等待者,则需要唤醒;
    遍历所有等待者,检查是否可以被唤醒,唤醒时,先授予锁,再唤醒,避够再次竞争;

等待者可以被唤醒的条件是:

  1. 等待者申请的锁模式与之前的等待者(没唤醒的),不会有锁冲突;
  2. 与已经持有锁者 不会产生锁冲突;
    如果产生冲突,都不会唤醒;
  • 清除本地锁记录并返回true;

结尾

非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!

作者邮箱:study@senllang.onaliyun.com

如有错误或者疏漏欢迎指出,互相学习。

注:未经同意,不得转载!

相关推荐
KellenKellenHao25 分钟前
MySQL数据库主从复制
数据库·mysql
liulilittle1 小时前
C++ i386/AMD64平台汇编指令对齐长度获取实现
c语言·开发语言·汇编·c++
@ chen1 小时前
Redis事务机制
数据库·redis
KaiwuDB1 小时前
使用Docker实现KWDB数据库的快速部署与配置
数据库·docker
小皮侠1 小时前
nginx的使用
java·运维·服务器·前端·git·nginx·github
不讲道理的柯里昂1 小时前
Vue MathJax Beautiful,基于Mathjax的数学公式编辑插件
vue.js·开源
一只fish1 小时前
MySQL 8.0 OCP 1Z0-908 题目解析(16)
数据库·mysql
泊浮目2 小时前
未来数据库硬件-网络篇
数据库·架构·云计算
静若繁花_jingjing2 小时前
Redis线程模型
java·数据库·redis
FIT2CLOUD飞致云2 小时前
多项功能优化与改进,1Panel开源面板v2.0.3版本发布
开源