Postgresql源码(144)LockRelease常规锁释放流程分析

相关
《Postgresql源码(69)常规锁细节分析》

最新遇到一个共享内存损坏导致常规锁释放报错warning "you don’t own a lock of type"的问题。

本篇对常规锁的概念做一些回顾,顺便分析下释放锁的流程。

  • SpinLock:❎
  • LWLock:❎
  • RegularLock:✅

基础概念回顾

《Postgresql源码(69)常规锁细节分析》

  • LockMethodLockHash:共享内存中的全局哈希表,所有进程可见。
    • 事务申请强锁、检测锁冲突或需要与其他事务协调时访问主锁表。
  • FastPathStrongRelationLocks:强锁标记表,共享内存中的计数器数组,用于快速判断是否存在强锁。
    • 快速筛选弱锁是否需进入主锁表流程,减少共享内存竞争。
  • LockMethodLocalHash:本地锁表,进程本地内存,每个后台进程独立维护。
    • 高频弱锁操作(如DML)的快速加锁与释放。

关于fastpath

  1. 先检查是否符合条件,EligibleForRelationFastPath,例如锁级别小于四,是表锁等。
  2. 去共享内存,FastPathStrongRelationLocks,查tag是否已经加过强锁了,加过就不能fastpath了。
  3. 如果都满足了,执行下面的,FastPathGrantRelationLock,开始加fast锁。加锁信息记录到PGPROC中。

《Postgresql源码(69)常规锁细节分析》

FastPathGrantRelationLock函数:

  • 使用PGPROC的数组Oid fpRelId[16];来保存OID。
  • 使用PGPROC的变量uint64 fpLockBits当做位图来记录锁级别。
    在这里插入图片描述
    FastPathGrantRelationLock逻辑:
  1. 3个bit一组按顺序查位图是不是空的,是空的就记录下来位置,不是空的就看下oid里面记的是不是需要的,如果正好Oid也是需要的,把当前请求锁模式或进去就可以返回了。
  2. 如果查了一遍位图,所有Oid都不是需要的,那就找一个空的位置,把锁级别记录到位图,OID记录到数组,然后返回。
  3. 如果查了一遍位图,没有一个空余位置,就返回false了。

LockRelease详细流程分析

bool
LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock)
{LOCKMETHODID lockmethodid = locktag->locktag_lockmethodid;LockMethod	lockMethodTable;LOCALLOCKTAG localtag;LOCALLOCK  *locallock;LOCK	   *lock;PROCLOCK   *proclock;LWLock	   *partitionLock;bool		wakeupNeeded;...

第一步 查本地锁表。
先查本地锁表,申请过的任何锁,一定会在本地锁表中存放,如果没有就会报错you don't own a lock of type,这里一般不会报错,本地锁表LockMethodLocalHash不在共享内存中,损坏的概率比较低。

	MemSet(&localtag, 0, sizeof(localtag)); /* must clear padding */localtag.lock = *locktag;localtag.mode = lockmode;locallock = (LOCALLOCK *) hash_search(LockMethodLocalHash,&localtag,HASH_FIND, NULL);if (!locallock || locallock->nLocks <= 0){elog(WARNING, "you don't own a lock of type %s",lockMethodTable->lockModeNames[lockmode]);return false;}

第二步 本地锁引用-1

  1. 锁在一个事务中可能被锁多次,但只有第一次锁才会走fastpath、主锁表,后面再加锁只会走本地锁表。
  2. 所以放锁时,如果发现是本地多次加锁的,只需要本地锁表-1即可。
  3. 这里还增加了多resowner的机制,因为可能有savepoint造成事务内 有多层子事务,这种情况下锁会被记录在多个resowner下,所以这里加了循环,遍历resowner,找到申请时使用的resowner,然后再ResourceOwnerForgetLock。
  4. 删完了一个顺便把数组最后一个挪到空洞位置,使数组始终是紧凑的。
	{LOCALLOCKOWNER *lockOwners = locallock->lockOwners;ResourceOwner owner;int			i;/* Identify owner for lock */if (sessionLock)owner = NULL;elseowner = CurrentResourceOwner;for (i = locallock->numLockOwners - 1; i >= 0; i--){if (lockOwners[i].owner == owner){Assert(lockOwners[i].nLocks > 0);if (--lockOwners[i].nLocks == 0){if (owner != NULL)ResourceOwnerForgetLock(owner, locallock);/* compact out unused slot */locallock->numLockOwners--;if (i < locallock->numLockOwners)lockOwners[i] = lockOwners[locallock->numLockOwners];}break;}}if (i < 0){/* don't release a lock belonging to another owner */elog(WARNING, "you don't own a lock of type %s",lockMethodTable->lockModeNames[lockmode]);return false;}}locallock->nLocks--;if (locallock->nLocks > 0)return true;

第三步 最后一次释放锁,需要真正把锁释放了。

尝试fastpath release,因为fastpath都是所在本地的,所以如果能释放也不需要知会主锁表。

这里需要给MyProc->fpInfoLock加LWLock的原因是:其他进程加强锁时,会遍历所有进程的PGPROC中fastpath记录的弱锁,将冲突的迁移到主锁表中。所以这里有可能被其他进程并发更新,所以需要LWLock。

	...if (EligibleForRelationFastPath(locktag, lockmode) &&FastPathLocalUseCount > 0){bool		released;LWLockAcquire(&MyProc->fpInfoLock, LW_EXCLUSIVE);released = FastPathUnGrantRelationLock(locktag->locktag_field2,lockmode);LWLockRelease(&MyProc->fpInfoLock);if (released){RemoveLocalLock(locallock);return true;}}

第四步:主锁表操作。

fastpath没删成,说明锁在主锁表中。

【共享内存】主锁表:LockMethodLockHash(存储所有锁对象)
【共享内存】锁进程关系表:LockMethodProcLockHash(查询当前进程阻塞了哪些进程,死锁检测)

  1. 开始操作主锁表,为了增加并发,这里按hashcode做了分区。如果不在一个分区中的锁可以并发。
  2. 在主锁表中定位锁对象。
  3. 在LockMethodProcLockHash定位PROCLOCK,用于查询当前进程阻塞了哪些进程
	partitionLock = LockHashPartitionLock(locallock->hashcode);LWLockAcquire(partitionLock, LW_EXCLUSIVE);lock = locallock->lock;if (!lock){PROCLOCKTAG proclocktag;Assert(EligibleForRelationFastPath(locktag, lockmode));lock = (LOCK *) hash_search_with_hash_value(LockMethodLockHash,locktag,locallock->hashcode,HASH_FIND,NULL);if (!lock)elog(ERROR, "failed to re-find shared lock object");locallock->lock = lock;proclocktag.myLock = lock;proclocktag.myProc = MyProc;locallock->proclock = (PROCLOCK *) hash_search(LockMethodProcLockHash,&proclocktag,HASH_FIND,NULL);if (!locallock->proclock)elog(ERROR, "failed to re-find shared proclock object");}LOCK_PRINT("LockRelease: found", lock, lockmode);proclock = locallock->proclock;PROCLOCK_PRINT("LockRelease: found", proclock);

如果下面报错,说明在主锁表和LockMethodProcLockHash都查到了,但是proclock->holdMask记录的lockmode和当前要release的不对应,报一个告警,把本地锁释放了,但不释放主锁表的信息。

	/** Double-check that we are actually holding a lock of the type we want to* release.*/if (!(proclock->holdMask & LOCKBIT_ON(lockmode))){PROCLOCK_PRINT("LockRelease: WRONGTYPE", proclock);LWLockRelease(partitionLock);elog(WARNING, "you don't own a lock of type %s",lockMethodTable->lockModeNames[lockmode]);RemoveLocalLock(locallock);return false;}
  • 调用 UnGrantLock 清除锁的授予状态(grantMask)和等待队列(waitMask),并标记是否需要唤醒等待进程

  • 通过 CleanUpLock 处理锁状态,若有等待进程,触发 LWLockWakeup 唤醒。

	/** Do the releasing.  CleanUpLock will waken any now-wakable waiters.*/wakeupNeeded = UnGrantLock(lock, lockmode, proclock, lockMethodTable);CleanUpLock(lock, proclock,lockMethodTable, locallock->hashcode,wakeupNeeded);LWLockRelease(partitionLock);RemoveLocalLock(locallock);return true;
}

LockRelease实例

这里截取drop table的一个中间态,给一个lock释放、并能走到主锁表释放的案例。

某一时刻查询到

postgres=# select * from pg_locks where relation=1214;
-[ RECORD 1 ]------+-----------------
locktype           | relation
database           | 0
relation           | 1214
page               |
tuple              |
virtualxid         |
transactionid      |
classid            |
objid              |
objsubid           |
virtualtransaction | 3/7
pid                | 1131328
mode               | RowExclusiveLock
granted            | t
fastpath           | f
waitstart          |

释放三级锁,没有走fastpath。

LockRelease (locktag=0x7ffd581db190, lockmode=3, sessionLock=false)

为什么没走fastpath?因为locktag_field1=0,表示这个表不属于某一个库,一般是是共享系统表(1214指的是pg_shdepend确实是共享系统表)。

(gdb) p	*locktag
$17 = {
locktag_field1 = 0, 
locktag_field2 = 1214, 
locktag_field3 = 0, 
locktag_field4 = 0, 
locktag_type = 0 '\000', 
locktag_lockmethodid = 1 '\001'}

释放锁时,两个主要数据结构的值:

(gdb) p	*lock
$19 = {tag = {locktag_field1 = 0, locktag_field2 = 1214, locktag_field3 = 0, locktag_field4 = 0, locktag_type = 0 '\000', locktag_lockmethodid = 1 '\001'}, grantMask = 8,waitMask = 0, procLocks = {head = {prev = 0x7f487fd34340, next = 0x7f487fd34340}}, waitProcs = {dlist = {head = {prev = 0x7f487e715988, next = 0x7f487e715988}}, count = 0},requested = {0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, nRequested = 1, granted = {0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, nGranted = 1}
(gdb) p	*proclock
$20 = {tag = {myLock = 0x7f487e715960, myProc = 0x7f4884dc4590}, groupLeader = 0x7f4884dc4590, holdMask = 8, releaseMask = 0, lockLink = {prev = 0x7f487e715978, next = 0x7f487e715978}, procLink = {prev = 0x7f4884dc4668, next = 0x7f4884dc4668}}

Lock

  • grantMask = 8:8 对应二进制 1000 对应 RowExclusiveLock。
  • waitMask = 0:资源无冲突请求
  • procLocks :指向自己,说明只有一个PROCLOCK关联当前这个锁。
  • requested[3] = 1​​:当前锁对象上有一个 RowExclusiveLock 模式的请求。
  • nRequested = 1​​:总请求次数为 1,与 requested[3] 一致。
  • ​granted[3] = 1:该请求已被成功授予

PROCLOCK

  • tag:关联的LOCK对象
  • holdMask = 8:与LOCK的grantMask一致,表示该进程在锁对象上持有RowExclusiveLock
  • releaseMask = 0:进程未触发锁释放操作
  • ​​lockLink​​:指向LOCK的procLocks链表,表示该PROCLOCK是链表中唯一的节点。
  • procLink​​:指向PGPROC的本地锁链表,表示该锁属于进程的本地锁管理范围。
/** Per-locked-object lock information:** tag -- uniquely identifies the object being locked* grantMask -- bitmask for all lock types currently granted on this object.* waitMask -- bitmask for all lock types currently awaited on this object.* procLocks -- list of PROCLOCK objects for this lock.* waitProcs -- queue of processes waiting for this lock.* requested -- count of each lock type currently requested on the lock*		(includes requests already granted!!).* nRequested -- total requested locks of all types.* granted -- count of each lock type currently granted on the lock.* nGranted -- total granted locks of all types.** Note: these counts count 1 for each backend.  Internally to a backend,* there may be multiple grabs on a particular lock, but this is not reflected* into shared memory.*/
typedef struct LOCK
{/* hash key */LOCKTAG		tag;			/* unique identifier of lockable object *//* data */LOCKMASK	grantMask;		/* bitmask for lock types already granted */LOCKMASK	waitMask;		/* bitmask for lock types awaited */dlist_head	procLocks;		/* list of PROCLOCK objects assoc. with lock */dclist_head waitProcs;		/* list of PGPROC objects waiting on lock */int			requested[MAX_LOCKMODES];	/* counts of requested locks */int			nRequested;		/* total of requested[] array */int			granted[MAX_LOCKMODES]; /* counts of granted locks */int			nGranted;		/* total of granted[] array */
} LOCK;/** We may have several different backends holding or awaiting locks* on the same lockable object.  We need to store some per-holder/waiter* information for each such holder (or would-be holder).  This is kept in* a PROCLOCK struct.** PROCLOCKTAG is the key information needed to look up a PROCLOCK item in the* proclock hashtable.  A PROCLOCKTAG value uniquely identifies the combination* of a lockable object and a holder/waiter for that object.  (We can use* pointers here because the PROCLOCKTAG need only be unique for the lifespan* of the PROCLOCK, and it will never outlive the lock or the proc.)** Internally to a backend, it is possible for the same lock to be held* for different purposes: the backend tracks transaction locks separately* from session locks.  However, this is not reflected in the shared-memory* state: we only track which backend(s) hold the lock.  This is OK since a* backend can never block itself.** The holdMask field shows the already-granted locks represented by this* proclock.  Note that there will be a proclock object, possibly with* zero holdMask, for any lock that the process is currently waiting on.* Otherwise, proclock objects whose holdMasks are zero are recycled* as soon as convenient.** releaseMask is workspace for LockReleaseAll(): it shows the locks due* to be released during the current call.  This must only be examined or* set by the backend owning the PROCLOCK.** Each PROCLOCK object is linked into lists for both the associated LOCK* object and the owning PGPROC object.  Note that the PROCLOCK is entered* into these lists as soon as it is created, even if no lock has yet been* granted.  A PGPROC that is waiting for a lock to be granted will also be* linked into the lock's waitProcs queue.*/
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 */
} PROCLOCKTAG;typedef struct PROCLOCK
{/* tag */PROCLOCKTAG tag;			/* unique identifier of proclock object *//* data */PGPROC	   *groupLeader;	/* proc's lock group leader, or proc itself */LOCKMASK	holdMask;		/* bitmask for lock types currently held */LOCKMASK	releaseMask;	/* bitmask for lock types to be released */dlist_node	lockLink;		/* list link in LOCK's list of proclocks */dlist_node	procLink;		/* list link in PGPROC's list of proclocks */
} PROCLOCK;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/79524.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于bert的情感分析程序

文章目录 任务介绍数据概览注意事项数据处理代码准备模型构建与训练模型类构建数据集构建数据批处理模型参数查看模型训练结果推理与评估模型推理准确率评估附录任务介绍 在当今信息爆炸的时代,互联网上充斥着海量的文本数据,如社交媒体评论、产品评价、新闻报道等。这些文本…

宇树科技举办“人型机器人格斗大赛”

2025 年 5 月至 6 月&#xff0c;一场全球瞩目的科技盛宴 —— 全球首场 “人形机器人格斗大赛”&#xff0c;将由杭州宇树科技盛大举办。届时&#xff0c;观众将迎来机器人格斗领域前所未有的视觉震撼。 为打造最强参赛阵容&#xff0c;宇树科技技术团队在过去数周里&#xf…

计算机视觉与深度学习 | 什么是图像金字塔?

图像金字塔详解 图像金字塔 图像金字塔详解1. **定义**2. **原理与公式****2.1 高斯金字塔****2.2 拉普拉斯金字塔**3. **代码示例****3.1 使用OpenCV实现****3.2 手动实现高斯模糊与降采样**4. **应用场景**5. **关键点总结**1. 定义 图像金字塔是一种多尺度图像表示方法,将…

Spring MVC注解式控制器开发

主要对Spring MVC的核心注解的应用进行了详细讲解&#xff0c;介绍了Controller和RequestMapping注解类型的相关知识。 1.注解式控制器概念 Spring2.5之前&#xff0c;我们都是通过实现框架提供的Controller接口来定义我们的处理器类。 Spring2.5引入注解式处理器支持&#…

thonny提示自动补全功能

THONNY IDE 自动补全功能配置 在 Thonny IDE 中启用和优化自动补全功能可以显著提升编程体验。为了确保该功能正常工作&#xff0c;需要确认几个设置选项。 配置自动补全 Thonyy IDE 的自动补全默认情况下是开启的。如果发现自动补全未按预期运行&#xff0c;可以通过调整首选…

D. 例题3.2.2 整数划分问题

题目描述 将正整数n表示成一系列正整数之和&#xff1a;nn_1n_2...n_knn1​n2​...nk​&#xff0c;其中8\geq n_1\geq n_2\geq ...\geq n_k\geq 18≥n1​≥n2​≥...≥nk​≥1&#xff0c;k\geq1k≥1。正整数n的这种表示称为正整数n的划分。 例如正整数6有如下11种不同的划分…

电脑RGB888P转换为JPEG方案 ,K230的RGB888P转换为JPEG方案

K230开发板本身具备将RGB888P转换为JPEG的能力&#xff0c;但需要正确调用硬件或软件接口。以下是具体分析及解决方案&#xff1a; 一、K230原生支持性分析 1. 硬件支持 K230的NPU&#xff08;神经网络处理器&#xff09;和图像处理单元&#xff08;ISP&#xff09;理论上支持…

图解 Git 工作流:理解 Rebase、Merge 与 Pull Request 的区别

图解 Git 工作流&#xff1a;理解 Rebase、Merge 与 Pull Request 的区别 在多人协作开发中&#xff0c;选择合适的 Git 分支管理策略至关重要。Merge、Rebase 和 Pull Request 是最常见的三种方式&#xff0c;它们本质不同&#xff0c;使用场景也不同。 本文将通过流程图&am…

Dart和Go语言特征对比

文章目录 Dart 和 Go 语法对照表字符串常用方法对照列表(数组/切片)常用方法对照Map (字典/哈希表) 使用对照IO 操作对照文件操作标准输入输出网络IO 主要差异说明 有同事说&#xff0c;我前端用Flutter&#xff0c;后端用Golang&#xff0c;都师出名门。但两个语言还是老打架&…

ActiveMQ 集群搭建与高可用方案设计(二)

五、高可用方案设计与优化 &#xff08;一&#xff09;Zookeeper 在 ActiveMQ 集群中的应用 作用&#xff1a;在 ActiveMQ 集群中&#xff0c;Zookeeper 扮演着至关重要的角色。它主要用于选举 Master 节点&#xff0c;通过其内部的选举机制&#xff0c;从众多的 ActiveMQ Br…

【项目归档】数据抓取+GenAI+数据分析

年后这两个月频繁组织架构变动&#xff0c;所以博客很久没更新。现在暂时算是尘埃落定&#xff0c;趁这段时间整理一下。 入职九个月&#xff0c;自己参与的项目有4个&#xff0c;负责前后端开发&#xff0c;测试&#xff0c;devops&#xff08;全栈/doge&#xff09;&#xff…

服务器热备份,服务器热备份的方法有哪些?

服务器热备份是保障业务连续性的重要技术手段&#xff0c;通过实时数据同步和快速故障切换&#xff0c;确保在主服务器故障时备份服务器能无缝接管。以下是常见的服务器热备份方法及其核心要点&#xff1a; 双机热备&#xff08;Active-Standby/Active-Active&#xff09; 主备…

【AI平台】n8n入门6:调用MCP服务(非社区节点)

前言 前边用n8n搭建一个MCP服务&#xff0c;现在&#xff0c;用n8n调用其他服务商提供的MCP服务。本文以高德地图服务为例&#xff0c;记录一下操作过程。 实现案例功能 MCP是啥 MCP&#xff08;Model Context Protocol&#xff0c;模型上下文协议&#xff09;是由Anthropi…

基于ArduinoIDE的任意型号单片机 + GPS北斗BDS卫星定位

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言1.1 器件选择1.2 接线方案 二、驱动实现2.1 核心代码解析&#xff08;arduino/ESP32-S3&#xff09; 三、坐标解析代码四、典型问题排查总结 前言 北斗卫星导航…

经典算法 最小生成树(prim算法)

最小生成树 题目描述 给定一个 n 个点 m 条边的无向图&#xff0c;图中可能存在重边和自环&#xff0c;边权可能为负数。 求最小生成树的树边权重之和。如果最小生成树不存在&#xff0c;则输出 impossible。 给定一张边带权的无向图 G (V, E)&#xff0c;其中&#xff1a…

LeetCode算法题 (设计链表)Day16!!!C/C++

https://leetcode.cn/problems/design-linked-list/description/ 一、题目分析 你可以选择使用单链表或者双链表&#xff0c;设计并实现自己的链表。 单链表中的节点应该具备两个属性&#xff1a;val 和 next 。val 是当前节点的值&#xff0c;next 是指向下一个节点的指针/引…

《解锁GCC版本升级:开启编程新世界大门》

《解锁GCC版本升级:开启编程新世界大门》 一、引言:GCC 版本升级的魔法钥匙 在编程的广阔天地里,GCC(GNU Compiler Collection)宛如一座灯塔,为无数开发者照亮前行的道路。它是一款开源且功能强大的编译器集合,支持 C、C++、Objective - C、Fortran、Ada 等多种编程语言…

toLua笔记

基本 LuaState luaStatenew LuaState(); luaState.Start(); luaState.DoString("xxx"); luaState.DoFile("yyy.lua"); luaState.Require("zzz");//不要加.lua后缀 luaState.CheckTop();//检查解析器栈顶为空 luaState.Dispose(); luaStatenull;…

go实现双向链表

需求 实现双向链表的节点生成、正反向遍历、指定删除。 实现 package mainimport ("fmt" )type zodiac_sign struct {number intdizhi stringanimal stringyear intprevious *zodiac_signnext *zodiac_sign }// 添加 // func add_node_by_order(pr…

AI实践指南:AGENT、RAG和MCP在Java中的简单实现

在当今AI快速发展的时代&#xff0c;有几个核心概念正在改变我们构建智能应用的方式。本文将用简单易懂的语言介绍三个重要概念&#xff1a;AGENT&#xff08;AI代理&#xff09;、RAG&#xff08;检索增强生成&#xff09;和MCP&#xff08;多通道感知&#xff09;&#xff0c…