存储过程本身不带锁,因其仅为pl/sql代码块,在各自会话事务中运行,不自动锁定数据;若无显式select...for update等锁机制,多会话并发更新同一行将导致覆盖写。

Oracle中多存储过程并发执行时,不能靠存储过程自身“加锁”,必须显式使用 SELECT ... FOR UPDATE 或事务级行锁机制,否则根本不存在真正意义上的悲观锁控制。
为什么存储过程本身不带锁
存储过程只是封装好的 PL/SQL 代码块,执行时默认在各自会话的事务上下文中运行。它不会自动锁定任何数据——哪怕两个过程都更新同一张表的同一行,若没加锁语句,就会发生覆盖写(lost update)。常见错误是以为“调用同一个存储过程”就天然串行化,实际完全不是。
-
CREATE OR REPLACE PROCEDURE定义本身不携带锁语义 - 过程内若只写
UPDATE ... WHERE id = ?,没有前置SELECT ... FOR UPDATE,就是纯乐观更新逻辑 - 多个会话并发执行该过程,可能同时读到旧值、同时计算、同时写入,后提交者覆盖前提交者
正确做法:在过程内嵌入 SELECT ... FOR UPDATE
悲观锁的核心动作是“先查后锁”,必须在修改前锁定目标行,并确保整个操作在同一个事务中完成。典型结构如下:
CREATE OR REPLACE PROCEDURE update_balance(p_acct_id NUMBER, p_amount NUMBER) AS
v_curr_bal NUMBER;
BEGIN
-- 关键:显式加锁,且必须指定 WAIT 或 NOWAIT 行为
SELECT balance INTO v_curr_bal
FROM accounts
WHERE acct_id = p_acct_id
FOR UPDATE WAIT 5; -- 等待最多 5 秒,超时抛 ORA-30006
<p>IF v_curr_bal + p_amount </p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill6971" title="QuantOracle"><img
src="https://img.php.cn/upload/skill/000/000/081/179120536643782.jpg" alt="QuantOracle" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill6971" title="QuantOracle" class="overflowclass">QuantOracle</a>
<p class="overflowclass">63个确定性量化金融计算器 + 10个通过MCP的复合工作流。期权定价、Greeks、奇异衍生品、风险指标、投资组合优化……</p>
</div>
<a rel="nofollow" href="/xiazai/skill6971" title="QuantOracle" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>UPDATE accounts SET balance = balance + p_amount
WHERE acct_id = p_acct_id;</p><p>COMMIT; -- 显式提交释放锁;若用自治事务则另当别论
EXCEPTION
WHEN TIMEOUT_ON_RESOURCE THEN
RAISE_APPLICATION_ERROR(-20002, 'Account locked by another session');
END;</p>
-
FOR UPDATE WAIT n是生产环境首选,避免无限阻塞 - 不要省略
INTO变量——SELECT ... FOR UPDATE必须有结果集绑定,否则语法报错 - 锁范围由
WHERE条件决定:条件越精确(如主键),锁粒度越小,竞争越低 - 如果过程被频繁调用,
WAIT 0(即NOWAIT)可快速失败,由应用重试或降级处理
容易踩的坑:事务边界与锁生命周期
锁只在事务结束(COMMIT 或 ROLLBACK)时释放。很多开发者忽略这点,导致锁意外长期持有:
- 忘记
COMMIT或ROLLBACK—— 锁持续到会话断开,可能卡住其他业务 - 在包(package)中使用全局变量缓存连接状态,误以为“过程结束=事务结束”,其实事务仍活跃
- 调用链中混用自治事务(
AUTONOMOUS_TRANSACTION):自治事务内的FOR UPDATE不影响外层事务锁,极易造成逻辑错乱 - 异常未捕获导致事务未回滚:
WHEN OTHERS THEN NULL是高危写法,应至少ROLLBACK并重抛
复杂点在于锁等待链和死锁风险
多个存储过程若按不同顺序访问多张表(如过程 A 先锁 orders 再锁 inventory,过程 B 反之),就可能触发 Oracle 自动检测到的死锁(ORA-00060)。这不是代码写错,而是设计缺陷:
- 所有涉及悲观锁的过程,必须严格约定资源访问顺序(如总是先锁主表再锁明细表)
- 避免在循环中逐条
SELECT ... FOR UPDATE,尤其当循环条件不可控时,锁数量和时间不可预估 - 监控锁等待最直接的方式是查
V$SESSION_WAIT和V$LOCK,但线上出问题时往往已晚——关键在设计阶段就规避
真正难的不是写对一行 FOR UPDATE,而是让所有相关过程在并发路径上保持一致的锁策略、顺序和事务粒度。一旦跨过程协作,单点加锁解决不了系统级竞争。










