正确使用DBMS_LOCK需先ALLOCATE_UNIQUE获取唯一handle再传给REQUEST,锁名≤100字节且仅含字母数字下划线,x_mode最常用,timeout建议设30而非0,release_on_commit必须显式设FALSE,RELEASE须在正常流程和EXCEPTION中均调用并校验handle非空。

DBMS_LOCK.request() 必须配合 ALLOCATE_UNIQUE 使用
直接传字符串给 DBMS_LOCK.REQUEST 是错的——Oracle 会把它当作锁名,但内部实际用的是锁句柄(handle),而硬编码字符串不经过句柄分配,会导致锁名截断、重复映射或权限异常。正确路径是先调 DBMS_LOCK.ALLOCATE_UNIQUE 拿到唯一 handle,再传给 REQUEST。
常见错误现象:锁名拼成 'MY_PROC_' || p_org_id 后直接传入 REQUEST,结果多个不同 p_org_id 值被截断为相同前缀(如超长时只留前 100 字节),造成误阻塞或漏锁。
-
ALLOCATE_UNIQUE返回的lockhandle是 RAW(128),必须声明为l_lockhandle VARCHAR2(128)或RAW(128),不能用VARCHAR2(32) - 锁名(
lockname参数)长度严格 ≤ 100 字节,含业务上下文如'INV_ADJ_' || TO_CHAR(p_warehouse_id) || '_' || p_item_code - 若
ALLOCATE_UNIQUE报 ORA-00600,大概率是锁名含空格、点号或斜杠,换成下划线即可
锁模式选 x_mode,timeout 别设 0 除非明确要“抢到就干”
业务级互斥基本只用 x_mode(值为 6),它保证同一时刻只有一个会话能拿到锁。其他模式如 s_mode 或 sx_mode 用于复杂协调场景,日常并发控制反而引入兼容性判断负担。
timeout 设为 0 表示“立即返回”,REQUEST 返回 1(超时)时你得立刻决定跳过或报错;但多数业务需要等待窗口,比如防止报表生成重叠,设 timeout => 30 更稳妥。
- 不要依赖默认 timeout(无限等待),否则一个卡死的会话会让后续所有请求挂住
-
release_on_commit => FALSE必须显式指定,因为 DBMS_LOCK 锁不随 COMMIT 自动释放 - 若用自治事务封装锁逻辑,自治事务里
RELEASE无效——锁属于外层会话,只能在外层释放
释放锁必须覆盖正常流程和 EXCEPTION 两个出口
忘记 DBMS_LOCK.RELEASE 是最危险的疏忽:锁残留后,同名请求永远阻塞,直到数据库重启或 DBA 手动清理 V$LOCK 中的锁记录。这不是事务回滚能解决的问题。
典型翻车点:在主流程末尾写了 RELEASE,但没在 EXCEPTION 块里写,一旦中间抛异常(如除零、NO_DATA_FOUND),锁就卡死了。
- 释放前检查 handle 是否非空:
IF l_lockhandle IS NOT NULL THEN DBMS_LOCK.RELEASE(l_lockhandle); END IF; - 不要把
RELEASE放进自治事务,也不要用 AFTER STATEMENT 触发器——它们无法感知当前会话锁状态 - 若过程被
RAISE_APPLICATION_ERROR终止,EXCEPTION块仍会执行,所以释放逻辑放这里最可靠
锁名必须带业务维度,且不能跨会话复用 handle
锁名不是随便起个名字就行。“my_batch_job” 这种硬编码锁名会让所有调用串行,哪怕参数完全不同;而“job_ || SYSDATE”又导致每次都不重用,失去互斥意义。关键是要让锁名精确反映互斥边界。
例如库存调整存储过程,按仓库 + 物料编码组合锁名:'STOCK_ADJ_' || p_wh_id || '_' || p_item_id。这样同一仓库同一物料的操作互斥,不同仓库可并行。
- 避免用
SYS_GUID()或DBMS_RANDOM.STRING生成锁名——每次值都不同,锁失效 - 同一个会话内多次调用同一过程时,不能复用上一次的
lockhandle,必须每次重新ALLOCATE_UNIQUE - 锁名中禁用中文、冒号、斜杠、空格,只用字母、数字、下划线,否则可能触发隐式字符集转换失败
x_mode 锁,比一行没提交的 SELECT FOR UPDATE 更隐蔽、更难排查。


















