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 *myLock和PGPROC *myProc。
4. PROCLOCK (进程锁契约结构体)
- 工程定义:这是锁管理器中最精妙的设计。它代表一个具体的后端进程对某个具体资源的持有状态。
- 核心字段:
tag:即上述的PROCLOCKTAG,作为寻址键。holdMask:当前进程对该资源私有的已持有锁级别掩码。releaseMask:在事务提交或回滚时,标记准备释放的锁掩码。
5. PGPROC (后端进程描述符)
- 工程定义:每个连接到数据库的客户端在操作系统层面对应一个物理进程,在共享内存中映射为
PGPROC结构体。它包含进程的 PID、事务 ID、以及底层的 OS 级同步原语(如 Latch/Semaphore),用于控制进程的物理休眠与唤醒。
二、 锁管理器的两大全局哈希表
为了实现 $O(1)$ 时间复杂度的并发寻址,内核在共享内存中预分配了两个巨大的全局泛型哈希表(HTAB):
LockMethodLockHash(资源哈希表):- 键 (Key):
LOCKTAG - 值 (Value):
LOCK结构体 - 作用:根据资源 ID 迅速定位目标资源的全局锁状态。
- 键 (Key):
LockMethodProcLockHash(契约哈希表):- 键 (Key):
PROCLOCKTAG - 值 (Value):
PROCLOCK结构体 - 作用:在事务结束时,供进程快速遍历并销毁自己名下的所有锁状态,防止内存泄漏。
- 键 (Key):
(注:这两个哈希表极其庞大,为了防止高并发下的多核 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数据库 锁管理器 相关:等您坐沙发呢!