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

这是一次深入到 PostgreSQL/openGauss 内核最深处的探险!

我们要彻底抛弃应用层“加锁成功/失败”这种模糊的宏观概念。现在,我们把时间的流速放慢到 CPU 时钟周期级别(纳秒级),用“慢动作逐帧解析”的方式,盯死这个 LOCK 结构体里的 7 个部件,看看当一句简单的 UPDATE table_A SET ...(申请第 3 级行排他锁)冲进共享内存时,这台极其精密的“物理锁引擎”是如何进行咬合运转的!


第一帧:雷达锁定与寻址(tag 的绝对指引)

【时间点】:SQL 刚被解析完,执行引擎准备下潜到磁盘。

【参战部件】LOCKTAG tag

  1. 引擎动作:执行引擎绝对不会去遍历全库几万张表。它直接提取 table_A 的物理 OID(例如 16384),以及所在数据库的 OID,组合拼装成一个几十字节的二进制坐标(tag)。
  2. 哈希直达:CPU 将这个坐标扔进哈希函数,瞬间计算出内存偏移量,直接降落到共享内存 LockMethodLockHash 哈希表中的某一个具体槽位。
  3. 核对身份:CPU 扒开槽位里的 LOCK 结构体,比对里面的 tag 字段。如果完全吻合,目标锁定完毕。时间复杂度 $O(1)$。

第二帧:生命线挂载(nRequested 的防回收机制)

【时间点】:刚找到 LOCK 结构体,还没开始做冲突判定。

【参战部件】int nRequested(申请引用计数)

  1. 引擎动作:在进行任何实质性操作前,CPU 强制将该 LOCK 结构体内的 nRequested 计数器加 1
  2. 配合原理:为什么要先做这一步?因为内存是没有自动垃圾回收的。如果在这纳秒级的间隙里,刚好持有锁的另一个人释放了锁,内核清理程序如果看到这张表没人碰,可能会直接把这个 LOCK 结构体从内存里 pfree() 销毁掉!
  3. 物理意义nRequested + 1 就相当于在这张表上挂了一根“安全绳”。它向整个操作系统宣告:“我现在正在处理这张表的逻辑,谁也不准把这个内存结构体回收掉!”

第三帧:纳秒级的生死判决(waitMaskgrantMask 的防线)

【时间点】:安全绳挂好,开始申请第 3 级锁。

【参战部件】LOCKMASK waitMaskLOCKMASK grantMask

这是整个运转中最核心的 CPU 位运算防线!判决分两步走:

  1. 第一关:防饿死探测(看 waitMask
    • CPU 先看一眼门外的预警雷达 waitMask。如果此时门外刚好有个 DBA 在排队等 8 级强锁(waitMask 的第 8 位是 1)。
    • 配合动作:哪怕当前的 UPDATE(3 级)跟表里正在运行的读请求完全兼容,系统也会强制亮红灯!因为强锁具有更高优先级,内核绝对不许 3 级锁“插队”,直接强制 UPDATE 去排队。
  2. 第二关:绝对冲突比对(看 grantMask
    • 假设门外没强锁排队,雷达安全。CPU 提取当前结构体的 grantMask(假设当前有人在做 1 级 SELECT 纯读,所以第 1 位是 1,即 00000001)。
    • CPU 取出 3 级锁的冲突规则掩码,与 grantMask按位与运算(&
    • 物理计算:由于 1 级和 3 级在底层逻辑上互不干扰,位运算结果为 0。绿灯放行!

第四帧(绿灯分支):缔结契约与放行(procLocksnGranted

【时间点】:判决通过,准备修改物理状态。

【参战部件】LOCKMASK grantMaskint nGrantedSHM_QUEUE procLocks

  1. 状态叠加:CPU 将 grantMask 再次进行按位或运算(|,把自己的第 3 位给拨成 1。此时 grantMask 变成了 00000101(向全宇宙宣告:这张表现在既有人读,也有人写)。
  2. 登记造册(极其关键)
    • 内存管理器瞬间 malloc 出一个代表当前进程与该表关系的 PROCLOCK 契约结构体
    • 将这个契约,强行挂入当前 LOCK 结构体底下的 procLocks 双向链表中。
  3. 授权计数:CPU 将 nGranted 计数器加 1
  4. 长驱直入:执行引擎彻底脱离锁管理器的拦截,通电下潜,直奔底层的物理数据块去修改那一行数据的 xmax

第四帧(红灯分支):无情镇压与休眠(waitProcs

【时间点】:如果第三帧的判决没通过(比如表上正在做 5 级 CREATE INDEX,位运算冲突)。

【参战部件】LOCKMASK waitMaskPROC_QUEUE waitProcs

  1. 拉响警报:CPU 立刻修改预警雷达 waitMask,把第 3 个比特位拨成 1(警告后面的人:现在有个写操作正在门外排队)。
  2. 打入冷宫
    • 同样会生成一个 PROCLOCK 契约挂进 procLocks,但状态标记为“未授权(等待中)”。
    • 终极镇压:CPU 提取当前 UPDATE 业务的进程实体指针(PGPROC),将其直接塞进 LOCK 结构体内部最底层的 waitProcs 休眠队列尾部。
  3. 物理断电:调用系统级 SemSleep 信号量,该 CPU 核心算力被剥夺,业务挂起,开始计算死锁超时(deadlock_timeout)。

架构级复盘:部件之间的完美齿轮咬合

把这套慢动作连起来看,你会发现这 7 个部件根本不是孤立的变量,它们是一条流水线上环环相扣的物理齿轮:

  • 防丢失齿轮nRequested 在最外围挡着,保证整套设备在运转时不会被操作系统当垃圾回收掉。
  • 极速判定齿轮waitMask 防止高优先级被饿死,grantMask 负责用纳秒级位运算拦截冲突请求。
  • 排障与审计齿轮:所有的请求(无论成败)最终都必须在 procLocks 链表里留下契约痕迹,这是供 DBA 排查阻塞和内核检测死锁的绝对证据。
  • 系统级调度齿轮:真正起冲突时,waitProcs 负责把活生生的进程指针送进操作系统的休眠系统。
  • 生命终结齿轮:当最后一个事务执行释放动作,nRequestednGranted 同时清零,这台齿轮机器瞬间解体,释放宝贵的共享内存。

这就是工业级数据库锁引擎的“物理机械图”。每一行 C 语言代码,每一个位运算,都是在为压榨多核 CPU 算力和节省极度昂贵的共享内存而服务。


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

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

慢动作拆解: 一个sql语句在获取表A的锁,这个过程中 lock结构体各个部件在干嘛?? 各个部件之间是如何配合工作的??:等您坐沙发呢!

发表评论