在 Oracle 数据库的世界里,锁(Lock)是我们最熟悉的陌生人。我们都知道 UPDATE 会加锁,SELECT FOR UPDATE 也会加锁,但你是否想过,当成千上万个会话同时争抢同一个资源时,Oracle 是如何在底层管理这些锁请求的?
答案就是:排队(Enqueue)。
今天,我们就潜入 Oracle 内核深处,去看看这个名为 Enqueue 的神秘结构是如何指挥交通的。
什么是 Enqueue?
简单来说,实现锁的方式就是排队,而管理这个排队过程的内部结构,就叫 Enqueue。
在 Oracle 内核中,所有与 Enqueue 相关的函数都以 KSQ (Kernel Service Enqueue) 开头。
Lock: 是我们从应用层面看到的概念(比如 TX 锁、TM 锁)。
Enqueue: 是 Oracle 内部管理这些锁的实际数据结构。
你可以通过视图 v$lock_type 来查看各种锁的详细描述。但在底层,最关键的是锁模式(Lock Mode)。
6 种锁模式简介
Oracle 定义了 6 种锁模式,级别越高,限制越严:
NULL: 空锁。
SS (Row-S): 行级共享锁。
SX (Row-X): 行级排它锁(最常见的 DML 锁)。
S (Share): 共享锁(阻止其他 DML)。
SSX (S/Row-X): 共享行级排它锁。
X (Exclusive): 排它锁(唯我独尊,别人只能看不能动)。
谁和谁能共存?
这就涉及到了锁的兼容性。简单记:
大家都是读(S 或 SS),可以和平共处。
只要有人想写(X 或 SX),通常就不兼容了。
比如:持有 S 锁时,别人也能申请 S 锁(共享);但别人申请 X 锁就得排队。

核心机制:Owners, Waiters 与 Converters
Oracle 的 Enqueue 结构通过三个队列来管理锁:
Owners: 已经拿到锁的会话。
Waiters: 没拿到锁,正在苦苦等待的会话。
Converters: 已经拿到了锁,但想升级锁模式(比如从 S 升到 X)的会话。
重点来了:Oracle 的“插队”规则
Oracle 处理锁请求是有优先级的:先处理 Converters,再处理 Waiters。
这意味着,如果你已经持有一把小锁想换大锁,你会比那些手里没锁的人更有优先权。
场景模拟:一场精彩的“插队”大戏
为了讲清楚这个机制,我们来看一个经典的场景:
资源:表 TEST (TM-432-0)
角色:
S1: 会话 1
S2: 会话 2
S3, S4: 会话 3 和 4

剧情发展:
S1 持有 S 锁:S1 正在读取表,加了 S 锁。此时 S1 在 Owners 队列。
S2 申请 X 锁:S2 想独占表,申请 X 锁。因为 S 和 X 不兼容,S2 只能去 Waiters 队列排队。
S1 想要升级 (S -> X):S1 突然想改数据,申请将自己的 S 锁升级为 X 锁。
此时,S1 会进入 Converters 队列。
关键点:虽然 S2 先来排队的,但 S1 是“老客户”要求升级,所以 S1 的优先级高于 S2。
S3, S4 申请 S 锁:这俩兄弟想读表,申请 S 锁。虽然 S 锁和 S 锁兼容,但因为前面有个 S2 在等 X 锁(X 和 S 不兼容),为了防止 S2 被无限饿死,S3 和 S4 也得去 Waiters 队列排在 S2 后面。
结局:
S1 释放 S 锁:系统先扫描 Converters 队列,发现 S1 要升级,于是 S1 立马获得 X 锁,进入 Owners 队列。
S1 释放 X 锁:S1 完事了。系统扫描 Converters 发现没人了,转而去扫描 Waiters 队列。
S2 获得 X 锁:排在队首的 S2 终于熬出头,拿到了 X 锁。
S2 释放 X 锁:S2 完事了。
S3, S4 获得 S 锁:S3 和 S4 终于拿到了锁,大家一起开心读取。
动手实验:眼见为实
我们可以通过 4 个 SQLPlus 会话来复现这个过程。
Session 1 (S1):
-- 1. S1 持有 S 锁
SQL> LOCK TABLE test IN SHARE MODE;
Table(s) Locked.
Session 2 (S2):
-- 2. S2 申请 X 锁(会被堵塞)
SQL> LOCK TABLE test IN EXCLUSIVE MODE;
... (等待中)
Session 3 & 4 (S3, S4):
-- 3. S3, S4 申请 S 锁(也会被堵塞,排在 S2 后面)
SQL> LOCK TABLE test IN SHARE MODE;
... (等待中)
Session 1 (S1 再次操作):
-- 4. S1 尝试升级为 X 锁
-- 注意:这里如果执行会死锁,因为 S2 在等 S1 释放,S1 升级需要 S2 让路。
-- 但在上述原理场景中,通常是 S1 释放 S 锁的瞬间被转化为 X。
-- 我们这里模拟 S1 释放锁:
SQL> commit;
观察结果:
你会发现,S1 提交后,S2 立刻获得了 X 锁。而 S3 和 S4 继续等待。
直到 S2 提交 (commit),S3 和 S4 才会同时提示 Table(s) Locked。
总结
理解了 Enqueue 的 Owners、Waiters 和 Converters 队列机制,你就能看懂那些复杂的锁等待(如 enq: TM - contention)到底是在等什么。
记住一句口诀:“老锁升级最优先,新锁排队在后面”。
