当前位置: 首页 > postgresql, 死锁 > 正文

把目光死死锁定在了 LockMethodProcLockHash(契约哈希表)的内部结构上,直接进入了**“内核架构师在写 C 语言代码时,到底往这块内存里塞了什么,才让系统运转起来”**的终极物理拆解阶段。

在这张契约哈希表中,真正存储的 Value 对象,就是我们在前面反复提到的核心结构体 —— PROCLOCK

在 PostgreSQL 的 C 语言源码(src/include/storage/lock.h)中,一个 PROCLOCK 结构体异常精简,仅有 5 个核心物理字段。内核架构师用这区区几十个字节,完美解决了状态追踪、权限鉴定和 $O(1)$ 寻址的三大绝境。

我们现在彻底化身底层 C 程序员,用最严密的工程逻辑,把这 5 个字段是怎么被“逼”出来的,一步步推导出来!


第一步绝境:哈希表怎么实现极速寻址?(推导 tag 字段

【物理推演】

哈希表(Hash Table)的命根子是 Key。如果我们要在一张拥有 100 万条记录的全局哈希表里,在 1 纳秒内找到“进程 A 和表 B 的契约”,这个 Key 该怎么设计?

  • 如果只用进程 A 的 ID,那查出来的是进程 A 拥有的所有契约,变成了 $O(N)$ 遍历。
  • 如果只用表 B 的 OID,查出来的是表 B 上的所有契约,同样是 $O(N)$ 遍历。

【内核决断:组合主键 PROCLOCKTAG tag

架构师将“人(进程)”和“物(资源)”的内存指针强行拼在了一起,作为这个结构体的“身份证”。

  • 结构体成分:包含 tag.myLock(指向唯一的 LOCK 结构体)和 tag.myProc(指向唯一的 PGPROC 结构体)。
  • 物理作用:它是第二张哈希表的绝对寻址依据。当 CPU 拿到这两个指针时,对其进行哈希散列计算,能以纯 $O(1)$ 的时间复杂度,瞬间定位到这个 PROCLOCK 所在的物理内存槽位。没有它,契约哈希表就失去了物理意义。

第二步绝境:全局状态的“失忆”与权限鉴定(推导 holdMask 字段

【物理推演】

我们知道,上游那 1 个 LOCK 结构体里有一个全局的 grantMask。假如 grantMask 的第 1 位和第 3 位是 1(代表当前这张表同时存在 1 级读锁和 3 级写锁)。

此时,进程 A 跑过来跟内核说:“我的事务干完了,我要释放 3 级写锁。”

  • 致命问题:内核怎么证明进程 A 真的拥有 3 级锁?如果它是乱报的,内核贸然去修改全局的 grantMask,整个并发控制系统就彻底崩溃了。全局的 grantMask 就像大锅饭,根本分不清谁吃了多少。

【内核决断:私有台账 LOCKMASK holdMask

  • 结构体成分:一个 32 位的二进制整数。
  • 物理作用:这是契约的**“私有台账”。它精准地记录了仅仅属于当前这个进程(PGPROC)**在这个资源上所持有的真实锁级别。
  • 逻辑闭环:当进程 A 申请释放 3 级锁时,CPU 直接读取 PROCLOCK_A -> holdMask 进行位运算。如果发现第 3 位是 0(根本没拿到),内核会直接报错拦截;如果是 1,才会将其清零,并安全地去扣减全局的 grantMask 份额。它保证了释放锁时的绝对安全性。

第三步绝境:长事务在锁释放时的微操灾难(推导 releaseMask 字段)

【物理推演】

假设进程 A 是一个极其复杂的长事务。它在第 1 分钟拿了 1 级锁,第 5 分钟升级成了 3 级锁。在第 10 分钟时,业务代码触发了一个极小的 ROLLBACK TO SAVEPOINT(回滚到内部保存点),要求只释放刚刚拿到的 3 级锁,但要保留早先拿到的 1 级锁。

  • 致命问题:如果 CPU 直接去暴力修改 holdMask 并重新计算全局状态,在极其频繁的锁升降级过程中,极易引发多核 CPU 计算全局 grantMask 的竞态条件,导致状态错乱。

【内核决断:异步释放缓冲区 LOCKMASK releaseMask

  • 结构体成分:一个 32 位的二进制整数。
  • 物理作用:这是一个**“待销毁缓冲池”**。当事务准备释放某些级别的锁时,并不立刻去粗暴修改全局状态,而是先在这个掩码里打个标记(把对应的比特位设为 1)。
  • 逻辑闭环:等到当前锁释放函数安全获取了底层的轻量级锁(LWLock)后,再统一把 releaseMask 里的份额,安全、原子地从 holdMask 和全局 grantMask 中扣除。这保证了在复杂事务生命周期中,锁降级微操的绝对物理安全。

第四步绝境:如何突破单向查询的孤岛?(推导 lockLinkprocLink 字段)

【物理推演】

现在我们有了哈希表($O(1)$ 查找),也有了私有台账掩码。但哈希表有一个致命弱点:它只能回答“是与否”,不能回答“有谁”

  • 场景 1(死锁检测):内核想知道“现在到底有哪几个人正在霸占表 B?”。哈希表回答不了,因为它需要你提供具体的人名才能查。
  • 场景 2(事务终结):进程 A COMMIT 了,内核想知道“进程 A 到底在这 1 万张表里的哪几张表上留下了契约?”。哈希表也回答不了。

【内核决断:双向十字链表节点 SHM_QUEUE

为了解决这种“顺藤摸瓜”的需求,架构师在 PROCLOCK 结构体内部,硬生生塞进了两个专门用于连接的双向链表节点。

  • SHM_QUEUE lockLink(物找人的绳子)
    • 物理作用:这个节点的前后指针,将自己死死挂在了上游 LOCK 结构体的 procLocks 链表上。
    • 逻辑闭环:有了它,死锁检测器只需查到 LOCK,就能顺着这条绳子,像拔萝卜一样把所有挂在上面的 PROCLOCK 拔出来,瞬间查清所有持有人的底细。
  • SHM_QUEUE procLink(人找物的绳子)
    • 物理作用:这个节点的前后指针,将自己死死挂在了下游 PGPROC 结构体的 myProcLocks 链表上。
    • 逻辑闭环:有了它,事务 COMMIT 时不需要遍历全库,只需顺着这根绳子,瞬间找到该进程名下的所有 PROCLOCK 契约,执行批量销毁。

架构师的终极视角汇总

通过这 4 步极其严密的物理推导,我们解剖出了 PROCLOCK 这个几十字节结构体的全貌:

这 5 个字段,没有任何一个是多余的,也没有任何一个是可以用其他逻辑替代的。它们在极度有限的共享内存中,严丝合缝地咬合在一起,构成了整个 PostgreSQL 并发锁系统的绝对核心骨架。

把这个结构体的内部字段像外科手术一样推导完,整个基于内存的锁体系机制不仅在理论上闭环,在 C 语言的物理落地上也彻底毫无死角了!


了解 www.876873.xyz 的更多信息

订阅后即可通过电子邮件收到最新文章。

LockMethodProcLockHash(契约哈希表) 的结构包含哪些内容?:等您坐沙发呢!

发表评论