这也是很多初学者在从业务开发转型底层 DBA 时,最容易卡住的认知盲区。因为在人类的直觉里,“锁”是挂在具体物品上的;既然有 1000 万个物品(数据行),就应该有 1000 万把锁。
但在 PostgreSQL 的工业级内核架构中,这根本不是魔法,而是一种极其冷酷的**“访问路径劫持(Access Path Hijacking)”与“状态降维”**。
我们现在彻底抛弃“锁数据”的错觉,完全站在执行引擎(Executor)和内存指针的物理视角,把“1 个结构体如何锁死 1000 万行数据”的逻辑,一步步推演出来。
第一步:物理隔离(认清数据与访问路径的鸿沟)
推演的起点,是你必须在脑海中把“数据”和“锁”在物理空间上彻底切开:
- 数据(1000 万行)的物理位置:它们安安静静地躺在**磁盘(Disk)**的物理数据块(Page)里。它们是没有任何主动行为能力的“死物”。
- 锁(
LOCK结构体)的物理位置:它驻留在**共享内存(Shared Memory)**的哈希网中。 - 工程公理:数据库里的任何 SQL,绝对不可能“瞬移”到磁盘上去摸数据。所有的 SQL,无论多么复杂,最终都必须由 CPU 里的**执行引擎(Executor)**转化为底层的 I/O 读写指令,才能下潜到磁盘。
推导结论一:锁,根本不需要挂在磁盘的数据身上。内核只需要在执行引擎通往磁盘的“必经之路”上,设一个收费站(拦截层)就可以了。
第二步:降维映射(将 1000 万行压缩为 1 个 OID)
既然要在“必经之路”上设卡,怎么标识这 1000 万行数据呢?
- 表级抽象:在关系型数据库的元数据体系里,无论表里是 1 行数据还是 10 亿行数据,它们都从属于同一个逻辑容器——表(Table)。
- 物理主键(OID):PG 在底层为每一张表分配了一个全局唯一的物理编号,叫
Relation OID(例如16384)。 - 结构体诞生:当需要锁表时,内核在共享内存的哈希表(
LockMethodLockHash)里,开辟一个几十字节的LOCK结构体。并且,把这个结构体的 Key(主键)死死绑定为这个OID 16384。
推导结论二:在这个结构体诞生的那一刻,1000 万行数据的物理存在,就被内核在逻辑上极其暴力地“降维压缩”成了仅仅一个数字(OID)。
第三步:强制亮证铁律(执行引擎的拦截逻辑)
有了收费站,也有了 OID 这个车牌号,接下来就是执行引擎的内核代码逻辑了。
PG 的内核架构师在执行引擎的源码里,写死了一道极其霸道的“强制亮证”关卡。当你的 SQL 想要去读取这 1000 万行数据中的任何一行时,必须遵守以下执行顺序:
- 翻译 SQL:将你要查的表名翻译成底层的
OID 16384。 - 阻断 I/O:执行引擎绝对不立刻向操作系统发起磁盘 I/O 请求。
- 内存查岗:执行引擎拿着
OID 16384,冲进共享内存的哈希网。它用 $O(1)$ 的极速时间复杂度,瞬间找到了那个与该 OID 绑定的LOCK结构体。 - 读取状态(
grantMask):执行引擎看向这个结构体内部的一个 32 位整数(grantMask)。这个整数的某个比特位(Bit)如果是1,就代表当前这张表正处于某种高级别的锁定状态(比如 8 级排他锁)。
推导结论三:执行引擎的底线是——不见兔子不撒鹰。在没有查验完内存里的这个结构体状态之前,任何进程连磁盘的边缘都摸不到。
第四步:实战动态推演(冲突爆发与全体阵亡)
现在我们让这套系统跑起来,看看 1 个结构体是怎么拦住千军万马的:
【起因】
某位 DBA 执行了一句 ALTER TABLE t ADD COLUMN...(申请 8 级排他锁)。
内核瞬间在内存里建好了那个 LOCK 结构体,并把 grantMask 的第 8 位拨成 1。
此时,磁盘上的 1000 万行数据毫无察觉,原封不动。
【拦截爆发】
就在下一秒,业务洪峰到来,1 万个并发连接同时发起了 SELECT * FROM t WHERE id = ?(申请 1 级读锁),它们企图去读取那 1000 万行数据里的不同行。
- 这 1 万个业务进程的执行引擎,同时拿着
OID 16384冲向内存哈希网。 - 它们同时撞上了那个唯一的
LOCK结构体。 - CPU 取出它们想要的 1 级锁,与结构体里的 8 级锁状态(
grantMask)做一次按位与运算(Bitwise AND)。 - 运算结果瞬间得出:绝对冲突!
- 全体挂起:内核毫不留情,直接将这 1 万个业务进程的上下文强行塞进该结构体底下的
waitQueue(双向等待链表)中。这 1 万个进程瞬间交出 CPU 算力,进入深度休眠。
【最终推导结论】
至此,推演结束。
这 1000 万行数据为什么被“锁”住了?
因为所有企图去磁盘上读取这 1000 万行数据的千军万马,都在大厦一楼大厅的那个几十字节的安检门(LOCK 结构体)处,被位运算的红灯瞬间拦下,全部被缴械并按倒在了内存的排队区里。
这根本不是“1 个结构体覆盖了 1000 万行数据”,而是“1 个结构体切断了通往这 1000 万行数据的唯一物理通道”。
这就是用 $O(1)$ 的内存开销,去抵挡 $O(N)$ 级别数据访问量的终极工程奥秘。
为什么 一个结构体 LOCK 就可以 锁住含有千万行数据的表?:等您坐沙发呢!