for update锁在显式事务中生效并持续至commit或rollback,open即加锁、close不释放;where无索引导致全表扫描及潜在锁表;nowait抢不到立即报错,skip locked跳过已锁行实现并发消费。

FOR UPDATE在PL/SQL里不是加个关键字就起作用,锁不锁得住、锁得准不准,全看事务控制、游标生命周期和WHERE条件是否走索引。
PL/SQL中FOR UPDATE的锁到底什么时候生效、什么时候释放?
它只在显式事务内有效,且锁持续到事务结束(COMMIT或ROLLBACK),不是语句执行完、也不是FETCH完就松开。常见误解是“OPEN游标就锁住,FETCH完就释放”,其实只要游标没CLOSE、事务没提交,锁就一直挂着。
-
OPEN cur FOR SELECT ... FOR UPDATE:此时行锁立即加上,其他会话对这些行做UPDATE或再次FOR UPDATE会被阻塞 -
FETCH只是取数据,不影响锁状态 -
CLOSE cur不会释放锁——锁只随事务结束而释放 - 如果用
EXECUTE IMMEDIATE动态执行SELECT ... FOR UPDATE,必须确保它在同一个事务块内,否则锁瞬间失效
为什么WHERE条件没走索引会导致锁表而不是锁行?
Oracle不会因为写了FOR UPDATE就自动优化锁粒度;当WHERE无法命中索引(比如隐式类型转换:id = '123'而id是NUMBER),执行计划变成全表扫描,虽然最终只锁命中的行,但过程中可能短暂持有大量缓冲区闩(latch),并发高时引发争用;更危险的是,某些旧版本或特殊配置下可能触发TM锁升级,导致整张表被阻塞。
- 用
EXPLAIN PLAN FOR SELECT ... FOR UPDATE确认ACCESS_PREDICATES含有效索引列 - 主键、唯一约束列最安全;普通字段务必建B-tree索引
- 避免
UPPER(name) = 'ABC'、LIKE '%xxx'这类无法走索引的写法 - 测试时查
v$locked_object,确认OBJECT_TYPE是TABLE还是ROW级别
NOWAIT和SKIP LOCKED在PL/SQL循环里怎么选?
二者语义完全不同:NOWAIT是“抢不到就立刻失败”,SKIP LOCKED是“跳过已锁的,拿下一个可用的”。在多进程轮询任务队列表的场景下,用NOWAIT只会让所有进程在第一条行上排队报错,而SKIP LOCKED才能真正实现并发消费。
-
SELECT ... FOR UPDATE NOWAIT:适合强抢占逻辑(如秒杀单号),捕获ORA-00054后应直接返回业务提示,而非盲目重试 -
SELECT ... FOR UPDATE SKIP LOCKED:必须配合FETCH FIRST 1 ROW ONLY或ROWNUM 限制返回条数,否则可能一次扫太多行影响性能 -
SKIP LOCKED不支持Oracle 11gR1及更早版本;也不能和NOWAIT或WAIT n共存,语法直接报错 - 注意:
SKIP LOCKED不保证顺序——如果业务要求“严格按创建时间处理”,它反而会破坏语义
最容易被忽略的三个实操陷阱
这三个点不写进代码里看不出问题,但上线后极易引发锁表、阻塞、数据错乱。
- AUTOCOMMIT默认开启:SQL*Plus或SQL Developer里没执行
SET AUTOCOMMIT OFF,每条语句执行完自动提交,FOR UPDATE锁瞬间释放,等于白加 - 游标未显式关闭+事务未结束:PL/SQL块中
OPEN后忘记CLOSE,又没COMMIT,锁长期悬挂,别人一查就卡住 - UPDATE的WHERE和SELECT的WHERE不一致:比如SELECT用
WHERE status = 'PENDING',UPDATE却用WHERE id = ?,而id非唯一——你锁的是一批行,改的却是另一批,根本没保护到目标数据
真正难的从来不是写出SELECT ... FOR UPDATE这行语法,而是判断该不该锁、锁哪几行、锁多久、锁不住时业务怎么兜底——这些没法靠数据库自己完成,得靠你在PL/SQL逻辑里把事务边界、错误分支、超时策略都写清楚。











