必须用select for update而非dbms_lock的场景是“读一行→判断状态→更新该行”,如订单状态流转或库存扣减;dbms_lock仅适用于动作级互斥(如防重复调度),不绑定数据行且需显式释放锁。

Oracle存储过程本身不自动并发控制,必须靠显式加锁或分片机制来隔离数据操作;否则多个会话同时调用,大概率出现覆盖写、状态错乱或死锁。
什么时候必须用 SELECT FOR UPDATE 而不是 DBMS_LOCK
当你需要「读一行 → 判断状态 → 更新该行」时,SELECT ... FOR UPDATE 是唯一正确选择。比如订单状态从 PENDING 改为 PROCESSED,或库存扣减前检查余量。
常见错误是误用 DBMS_LOCK.REQUEST 替代它:这个函数锁的是一个字符串名(如 'ORDER_123'),不绑定具体行,也不阻止其他会话对同一行执行 UPDATE 并提交——你的业务逻辑可能直接覆盖别人刚改的值。
- 必须带
INTO子句,否则 PL/SQL 编译报错 - 务必加
WAIT 5,避免无限挂起;不要依赖默认的无限等待 -
OF column_name不是可选:只锁指定列能减少争用,尤其表字段多时 - 异常分支里必须
ROLLBACK,否则锁不释放,后续请求全卡住
DBMS_PARALLEL_EXECUTE 分片执行的三个硬约束
想让一个耗时存储过程并行跑,核心不是“开多线程”,而是把它改造成按 ROWID 范围分段执行。否则并发只是制造竞争。
典型翻车点:COMMIT 写在过程里会报 ORA-14551;SQL 字符串里拼接变量导致注入和绑定失败;普通账号缺 CREATE JOB 权限根本跑不起来。
- 每个 chunk 的执行体必须封装成接受
:start_rid和:end_rid参数的过程,例如:BEGIN serial(:start_rid, :end_rid); END; -
RUN_TASK内部已隐式 commit,外部过程里再写COMMIT就报错 - 必须用绑定变量,或通过
DBMS_PARALLEL_EXECUTE.GET_CHUNK_ROWID拿当前范围,别手拼 SQL - 需要
GRANT CREATE JOB TO your_user,开发账号常被漏掉,得找 DBA 补
DBMS_LOCK 只适合“动作级互斥”,且必须手动释放
它真正管用的场景,是防止两个定时任务同时启动报表生成、或前端重复点击触发同个后台操作——这些不依赖某行数据状态,只关心“此刻有没有别人在干这事”。
但它的坑极深:锁不随事务结束自动释放,COMMIT 后锁还在;没死锁检测,两个会话互相等对方的锁,Oracle 不报 ORA-00060,而是无限等待;锁名长度不能超 128 字符,且必须全局唯一。
- 先调
DBMS_LOCK.ALLOCATE_UNIQUE获取 handle,别直接传字符串进去 -
timeout => 0表示立即返回,适合“抢到就干,抢不到就退”的场景 - 业务逻辑结束后,必须显式调
DBMS_LOCK.RELEASE,否则锁可能残留数小时甚至跨会话 - 锁名建议带业务含义,如
'DAILY_INVENTORY_SYNC',别用时间戳或随机数(易冲突)
真正难的不是写哪条语句,而是判断该用哪套机制:行状态驱动的流程,绕不开 SELECT FOR UPDATE;批量数据处理,得靠 DBMS_PARALLEL_EXECUTE 分片;而纯动作协调,DBMS_LOCK 才有存在意义——混用或跳过其中任一环,问题往往在压测时才暴露。











