这是一次完全剥离所有修辞、抛弃一切应用层概念的纯物理级推演。
我们将直接下潜到 PostgreSQL 内核共享内存区,仅使用 C 语言的 PGPROC(进程状态)、LOCK(资源实体)、PROCLOCK(持有契约) 这三大核心结构体,以及底层的双向链表(Doubly Linked List)和操作系统信号量(Semaphore),把死锁的完整生命周期,按 CPU 执行的绝对物理时序,逐帧推导出来。
第一帧:合法占有与指针的确立(T0 时刻)
【内存状态基线】
系统中有两个并发进程:PGPROC_1 和 PGPROC_2。
有两张表被并发访问,在哈希网中对应两个资源结构体:LOCK_A 和 LOCK_B。
PGPROC_1的物理动作:成功申请LOCK_A的排他锁。- 内存管理器
malloc出PROCLOCK_1A。 - 状态字段
PROCLOCK_1A->holdMask记录 8 级锁,标记granted = true。 - 指针挂载:该契约被双向插入到
LOCK_A->procLocks链表和PGPROC_1->myProcLocks链表中。
- 内存管理器
PGPROC_2的物理动作:成功申请LOCK_B的排他锁。- 内存管理器
malloc出PROCLOCK_2B,标记granted = true。 - 指针挂载:被双向插入到
LOCK_B->procLocks和PGPROC_2->myProcLocks链表中。
- 内存管理器
第二帧:交叉碰撞与物理挂起(死锁的绝对形成)
【物理动作:PGPROC_1 申请 LOCK_B】
PGPROC_1的 CPU 提取LOCK_B->grantMask进行按位与运算,结果不为 0(因为PGPROC_2正拿着排他锁)。冲突成立。- 生成排队契约
PROCLOCK_1B,标记granted = false,挂入LOCK_B->procLocks链表。 - 关键指针修改:内核修改
PGPROC_1->waitLock = LOCK_B(记录当前进程到底被卡在哪个资源上)。 - 入列与断电:内核将
PGPROC_1的指针塞入纯休眠队列LOCK_B->waitProcs的尾部。随后,调用操作系统原语SemSleep,PGPROC_1释放 CPU 算力,陷入深度休眠。
【物理动作:PGPROC_2 申请 LOCK_A】
- 同理,
PGPROC_2在LOCK_A->grantMask处遭遇按位与运算冲突。 - 生成
PROCLOCK_2A(granted = false),挂入LOCK_A->procLocks。 - 关键指针修改:内核修改
PGPROC_2->waitLock = LOCK_A。 - 入列与断电:
PGPROC_2指针被塞入LOCK_A->waitProcs尾部,调用SemSleep陷入深度休眠。
【推导结论】:
此时,物理闭环正式形成。两个业务进程全部处于 OS 级别的休眠态,CPU 占用率为 0。系统没有任何报错日志,处于绝对的物理死寂。
第三帧:时间复杂度的妥协与硬件中断(定时器触发)
【架构逻辑】:
如果内核在每次产生 SemSleep 时都去遍历全库指针找闭环,图遍历算法 $O(V+E)$ 的时间复杂度会引发极高的 CPU 算力消耗。因此,内核采用惰性检测(Lazy Detection)。
- 时钟倒计时:当
PGPROC_1和PGPROC_2进入休眠时,内核启动了由deadlock_timeout参数控制的硬件定时器(默认 1000ms)。 - 中断打断:1000ms 后,操作系统的时钟中断触发
SIGALRM信号。 - 强制唤醒:该信号强行将
PGPROC_1(假设它先跨过 1 秒门槛)从休眠态拉起。 - 状态确认:
PGPROC_1醒来后,核对自己的PROCLOCK_1B->granted,发现依然是false(说明不是因为拿到锁醒来的)。它立刻被内核控制流强制重定向,进入死锁检测核心函数:CheckDeadLock()。
第四帧:内存指针的图论遍历(WFG 有向图的构建)
PGPROC_1 现在化身为侦探,开始在内存中顺着指针寻找闭环。为了保证遍历时数据结构不被其他进程修改,PGPROC_1 瞬间申请并持有了锁管理器的全局轻量级闩锁(LWLock)。
- 起点查询:
PGPROC_1读取自身的阻塞源:PGPROC_1->waitLock。指针指向了LOCK_B。 - 寻找持锁者(找边):
PGPROC_1遍历LOCK_B->procLocks链表,寻找其中granted == true的节点。命中PROCLOCK_2B。顺着该契约的指针,找到了PGPROC_2。(图论确立:建立有向边PGPROC_1$\rightarrow$PGPROC_2) - 递归下潜:
PGPROC_1读取PGPROC_2的阻塞源:PGPROC_2->waitLock。指针指向了LOCK_A。 - 寻找次级持锁者:
PGPROC_1遍历LOCK_A->procLocks链表,寻找granted == true的节点。命中PROCLOCK_1A。顺着该契约的指针,读取到了内存地址 —— 正是PGPROC_1自身的内存地址!(图论确立:建立有向边PGPROC_2$\rightarrow$PGPROC_1) - 闭环实锤:DFS(深度优先搜索)递归栈在比对内存地址时,发现起点与终点地址完全重合。一个完美的拓扑学环(Cycle)在 C 语言的指针链路中被绝对证明。
第五帧:内存结构的强行撕裂与系统恢复(物理斩杀)
在确认全是硬冲突(无法通过重排 waitProcs 队列顺序解开闭环)后,内核进入异常处理分支。
- 牺牲者选定与报错:通常触发检测的进程(即
PGPROC_1)被选为牺牲者。它向前端抛出ERROR: deadlock detected。 - 事务回滚(核心清理):
PGPROC_1的执行引擎触发AbortTransaction()。- 物理摘除:内核沿着
PGPROC_1->myProcLocks链表,将PROCLOCK_1B从LOCK_B的链表中unlink(摘除),并将PROCLOCK_1A从LOCK_A的链表中unlink。 - 释放内存:调用
pfree()销毁这两个契约结构体。LOCK_A瞬间变为无主状态。
- 物理摘除:内核沿着
- 状态重算与精准唤醒:
- 内核重新计算
LOCK_A->grantMask(由于PGPROC_1的死亡,该掩码归零)。 - 内核读取
LOCK_A->waitProcs休眠队列的头部,提取出排在第一位的PGPROC_2的指针。 - 内核调用系统底层指令
SemWakeup,向PGPROC_2的 CPU 核心发送通电信号。
- 内核重新计算
- 闭环破除:
PGPROC_2苏醒,顺利执行grantMask的位运算并拿到LOCK_A。系统吞吐量恢复,死锁从物理层面上被彻底清除。
这就是一个死锁从合法获取、到指针死结、再到硬件中断拉起、最后 DFS 算法破环的纯工业级工程推演。
了解 www.876873.xyz 的更多信息
订阅后即可通过电子邮件收到最新文章。
postgresql数据库的 死锁,逻辑推导过程:等您坐沙发呢!