Oracle中SELECT...FOR UPDATE是原生行级排他锁,需显式事务控制、索引优化WHERE条件,并合理选用NOWAIT/WAIT n/SKIP LOCKED避免阻塞。
Oracle 中 SELECT ... FOR UPDATE 是悲观锁的核心手段
它不是“模拟”或“包装”出来的锁,而是 oracle 原生支持的、在事务中直接申请行级排他锁(tx lock)的语句。只要在 begin 事务块内执行,且后续有 commit 或 rollback,锁就会真实生效并阻塞其他会话对同一行的 dml 或再次 for update。关键不在于“怎么写”,而在于“怎么用才不出错”。
FOR UPDATE 必须配合事务显式控制
Oracle 默认自动提交(autocommit),但 FOR UPDATE 只在未提交的事务中维持锁。一旦执行完 SELECT ... FOR UPDATE 就退出会话或隐式提交,锁立刻释放——等于没锁。
- 必须先
BEGIN(或用 PL/SQL 块包裹),再执行SELECT ... FOR UPDATE - 后续必须跟
UPDATE/DELETE等操作,最后以COMMIT或ROLLBACK结束 - 不要在应用层用 JDBC/ODBC 的 auto-commit 模式下单独执行该语句;否则锁瞬间释放
- PL/SQL 过程中若用
EXECUTE IMMEDIATE动态执行FOR UPDATE,需确保整个逻辑在同一个事务上下文中
避免意外锁表:WHERE 条件必须命中索引
如果 WHERE 子句无法走索引(比如对非索引字段查询、函数包裹字段、隐式类型转换),Oracle 会升级为表级锁(TM lock,mode=6),整张表被阻塞——这是生产事故高发点。
- 检查执行计划:
EXPLAIN PLAN FOR SELECT ... FOR UPDATE,确认ACCESS_PREDICATES含有效索引列 - 主键、唯一约束列天然安全;普通字段务必建 B-tree 索引
- 慎用
LIKE '%xxx'、UPPER(name) = 'ABC'等无法利用索引的写法 - 测试时用
v$locked_object和dba_objects联查,确认OBJECT_TYPE是TABLE而非意外出现多个ROW锁对象
NOWAIT、WAIT n 和 SKIP LOCKED 的实际取舍
默认行为是无限等待,极易导致线程挂起甚至超时中断。真实场景中几乎总要加控制子句:
-
FOR UPDATE NOWAIT:立即失败,抛ORA-00054;适合“抢锁失败就放弃”的调度类任务(如任务分发) -
FOR UPDATE WAIT 3:最多等 3 秒,超时仍报ORA-00054;适合有明确响应时限的交互流程 -
FOR UPDATE SKIP LOCKED:跳过已被锁的行,返回可用行;这是高并发轮询场景(如订单处理、消息消费)唯一靠谱的选择,否则节点间会严重串行化 - 注意:
SKIP LOCKED在 Oracle 11gR2+ 才完全支持,旧版本慎用
真正难的不是写对语法,而是判断哪一行该锁、锁多久、锁不住怎么办——这些没法靠 FOR UPDATE 自己解决,得结合业务重试逻辑、超时策略和监控告警一起设计。











