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

PostgreSQL 数据库中,锁管理器(Lock Manager) 并不是一个独立的后台进程,而是一套存在于**共享内存(Shared Memory)**中的极其精密的数据结构和算法集合。所有的数据库读写进程(Backend Processes)在触碰底层数据之前,都必须“自觉”地按照同一套 C 语言算法,来到这片共享内存区域进行登记和冲突判定。

PostgreSQL/openGauss 的锁管理器(Lock Manager)在物理层面上,本质上是一个基于共享内存(Shared Memory)构建的、由轻量级分区锁(Partitioned LWLocks)保护的、高度并发的泛型哈希寻址与位运算状态机

一、 锁管理器的核心 C 语言结构体拓扑

在源码 src/include/storage/lock.h 中,锁管理器的物理实体由 5 个绝对核心的结构体交织而成。它们构成了“资源-锁-进程”的完整寻址链条。

1. LOCKTAG (资源标识结构体)

  • 工程定义:用于在共享内存中绝对唯一地定位一个物理资源(如表、页、元组、事务 ID)。
  • 物理构成:通常包含 4 个 32 位的无符号整数(如 dbId, relId 等)和一个锁类型标识。这 16 或 20 个字节在内存中是连续的,作为后续哈希函数的物理输入(Hash Key)。

2. LOCK (资源锁实体结构体)

  • 工程定义:代表共享内存中被锁定的资源实体,维护该资源上所有并发进程的宏观全局状态。
  • 核心字段
    • tag:即上述的 LOCKTAG
    • grantMask (已授权掩码):一个整型变量。按位运算记录当前该资源上已经被授予了哪些级别的锁(例如第 3 位为 1,代表存在 RowExclusiveLock)。
    • waitMask (等待掩码):记录当前哪些级别的锁正在排队等待。
    • procLocks:一个双向链表头指针。链接所有当前持有或正在等待该资源的 PROCLOCK 结构体。
    • waitProcs:一个专门的等待队列链表(类型为 PROC_QUEUE),严格按优先级和先来后到顺序记录处于休眠状态的后端进程。

3. PROCLOCKTAG (关系标识结构体)

  • 工程定义:唯一标识“某一个后端进程”与“某一个具体的 LOCK 实体”之间的关联关系。
  • 物理构成:包含两个底层的 C 语言指针:LOCK *myLockPGPROC *myProc

4. PROCLOCK (进程锁契约结构体)

  • 工程定义:这是锁管理器中最精妙的设计。它代表一个具体的后端进程对某个具体资源的持有状态
  • 核心字段
    • tag:即上述的 PROCLOCKTAG,作为寻址键。
    • holdMask:当前进程对该资源私有的已持有锁级别掩码。
    • releaseMask:在事务提交或回滚时,标记准备释放的锁掩码。

5. PGPROC (后端进程描述符)

  • 工程定义:每个连接到数据库的客户端在操作系统层面对应一个物理进程,在共享内存中映射为 PGPROC 结构体。它包含进程的 PID、事务 ID、以及底层的 OS 级同步原语(如 Latch/Semaphore),用于控制进程的物理休眠与唤醒。

二、 锁管理器的两大全局哈希表

为了实现 $O(1)$ 时间复杂度的并发寻址,内核在共享内存中预分配了两个巨大的全局泛型哈希表(HTAB):

  1. LockMethodLockHash (资源哈希表)
    • 键 (Key)LOCKTAG
    • 值 (Value)LOCK 结构体
    • 作用:根据资源 ID 迅速定位目标资源的全局锁状态。
  2. LockMethodProcLockHash (契约哈希表)
    • 键 (Key)PROCLOCKTAG
    • 值 (Value)PROCLOCK 结构体
    • 作用:在事务结束时,供进程快速遍历并销毁自己名下的所有锁状态,防止内存泄漏。

(注:这两个哈希表极其庞大,为了防止高并发下的多核 CPU 争抢,它们由 16 把底层分区的轻量级锁 LWLock 进行物理切片保护。)


三、 加锁机制的慢动作逻辑推导 (LockAcquire 流程)

当执行引擎解析到一句 UPDATE t1 SET ...,需要对 t1 表申请 3 级锁(RowExclusiveLock)时,底层的 LockAcquireExtended 函数开始执行。以下是微秒级的物理执行时序:

步骤 1:构建物理标识 (Tag Construction)

后端进程在自身的局部栈内存(Local Stack)中,提取 t1 表的 OID 和当前数据库的 OID,组装成一个连续字节的 LOCKTAG

2. 哈希寻址与分区闩锁获取 (Hash & LWLock Acquisition)

进程将 LOCKTAG 传递给底层的哈希引擎(hash_any),计算出一个哈希值。

内核根据哈希值,定位到目标分区,并向操作系统申请该哈希分区的独占轻量级锁(LWLockAcquire)

逻辑断言:此时,该分区的哈希表被当前进程物理锁定,其他试图操作该分区的 CPU 核心进入 Spinlock(自旋)状态。

3. 资源实体寻址与初始化 (LOCK Lookup/Create)

进程在 LockMethodLockHash 中利用哈希值进行 $O(1)$ 查找。

  • 如果表是第一次被访问,未命中。内核在共享内存中 malloc 分配一个新的 LOCK 结构体,初始化状态为空,并插入哈希表。
  • 如果命中,直接获取该 LOCK 结构体的内存指针。

4. 契约实体寻址与初始化 (PROCLOCK Lookup/Create)

进程紧接着将当前自身的 PGPROC 指针与获取到的 LOCK 指针打包成 PROCLOCKTAG,去 LockMethodProcLockHash 中寻址。

如果未命中,分配并初始化一个新的 PROCLOCK 结构体,将其挂载到 LOCK 结构体的 procLocks 双向链表中。

5. 绝对核心:位运算冲突检测 (Conflict Detection)

此时进入锁管理器的核心逻辑枢纽。内核使用预定义的**冲突掩码矩阵(Conflict Matrix)**进行按位与(Bitwise AND)运算:

提取请求的锁级别掩码(例如 3 级锁对应掩码位),与 LOCK->grantMask(当前已被其他人持有的所有锁掩码)以及 LOCK->waitMask(正在排队的掩码)进行按位逻辑 & 计算。

分支 A:无冲突 (Fast Path / Grant)

  • 如果按位运算结果为 0,表示无冲突。
  • 内核执行状态机变更:将 LOCK->grantMask 对应位标记为 1,将 PROCLOCK->holdMask 对应位标记为 1。
  • 释放分区的 LWLock
  • 函数返回 LOCKACQUIRE_OK,执行引擎继续向下执行物理 I/O 操作。

分支 B:发生物理冲突 (Wait / Sleep)

  • 如果按位运算结果非 0,表示发生互斥。
  • 进程将 LOCK->waitMask 的对应位标记为 1,声明自己进入排队状态。
  • 进程将自身的 PGPROC 结构体指针追加到 LOCK->waitProcs 队列的尾部(或按等待优先级插入)。
  • 物理挂起:进程释放分区的 LWLock(必须释放,否则导致整个系统死锁),随后调用操作系统的同步原语(如 Linux 下的 epoll 或 SysV Semaphore),让当前进程进入中断睡眠状态(Sleep)
  • 此时,该进程的 CPU 时间片被操作系统剥夺,宏观表现为 SQL 语句“卡住等待”。

6. 唤醒与闭环 (Wakeup)

当持有锁的阻断进程执行 COMMIT,它会在清理 PROCLOCK 和修改 LOCK->grantMask 后,扫描 waitProcs 队列。

发现冲突解除后,阻断进程会调用 OS 级的 SetLatch 或信号量释放动作,将被挂起的后端进程唤醒。被唤醒的进程重新获取 CPU 调度,完成状态校验后,继续执行。


架构师总结与下一步规划

锁管理器的本质,是为了在极高并发的多进程环境下,通过极低开销的哈希寻址位掩码运算,维护资源访问的一致性,并通过 OS 原语接管进程的生命周期调度。


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

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

postgresql数据库 锁管理器 相关:等您坐沙发呢!

发表评论