DBMS_LOCK不能替代行级锁,仅提供会话级命名互斥锁;真正需行锁的场景(如状态判断后更新)必须用SELECT...FOR UPDATE配合WAIT、OF子句及异常处理。
dbms_lock 不能实现行级锁,它只提供会话级互斥锁(mutex),和 select ... for update 的语义完全不同。 想用它替代行锁来控制单条记录的并发访问,结果一定是错的——要么锁不住,要么锁过头。
DBMS_LOCK.REQUEST 实际上锁的是什么
它基于一个字符串标识(lockname)在实例范围内建立全局锁名注册。一旦某个会话调用 DBMS_LOCK.REQUEST 成功,其他会话再用相同 lockname 尝试获取,就会阻塞或失败。
这意味着:
- 锁粒度是“名字”,不是“行”。你传
'ORDER_1001',所有想操作订单 1001 的会话都会被串行化;但如果你传的是固定值如'PROC_SYNC_LOCK',整个存储过程就变成全局单线程了 - 它不感知事务边界:
COMMIT或ROLLBACK不会自动释放锁,必须显式调用DBMS_LOCK.RELEASE,否则锁可能长期滞留 - 没有自动死锁检测:两个会话互相等待对方的
DBMS_LOCK,Oracle 不报ORA-00060,而是无限等待(除非设了timeout)
真正该用 SELECT ... FOR UPDATE 的场景
当你要「基于某条记录的状态做判断并更新」时,必须用行锁。比如入库单状态检查、库存扣减、任务状态流转等。
正确写法要点:
- 必须带
INTO子句,不能光写SELECT ... FOR UPDATE—— PL/SQL 编译器会报错 - 务必加
WAIT n(如WAIT 5),避免无限挂起;不要依赖默认行为 -
OF子句不是可选装饰:只锁指定列能减少锁争用,尤其当表有大量字段时 - 异常分支里必须
ROLLBACK,否则锁不会释放,后续请求全卡住
示例片段:
DECLARE
v_status VARCHAR2(10);
BEGIN
SELECT status INTO v_status
FROM order_hdr WHERE order_id = 'ORD-20260428-1'
FOR UPDATE OF status WAIT 5;
IF v_status = 'PENDING' THEN
UPDATE order_hdr SET status = 'PROCESSED' WHERE order_id = 'ORD-20260428-1';
END IF;
COMMIT;
EXCEPTION
WHEN NO_DATA_FOUND THEN
ROLLBACK;
WHEN TIMEOUT_ON_RESOURCE THEN
ROLLBACK;
RAISE_APPLICATION_ERROR(-20001, 'Lock timeout on order');
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END;
DBMS_LOCK 唯一合理的用途
它适合做「跨事务的粗粒度协调」,比如:
- 限制某个批处理作业同一时间只能运行一个实例(用固定锁名
'BATCH_JOB_X') - 在 DBMS_SCHEDULER 任务之间做简单顺序控制(配合
DBMS_LOCK.ALLOCATE_UNIQUE动态生成锁名) - 临时规避无法修改的遗留代码中的竞态问题(但应尽快重构)
注意:DBMS_LOCK 需要显式授权,且从 Oracle 23c 起已标记为 deprecated,未来版本可能移除。
最易被忽略的一点:很多人把 SELECT ... FOR UPDATE 放在存储过程开头就以为万事大吉,但没意识到——如果业务逻辑中间有 COMMIT,锁立刻释放;之后再查再更新,就又回到竞态起点。行锁只活到事务结束,不是整个存储过程生命周期。


















