oracle的for update只锁定查询返回的行,且仅对当前事务可见;其他会话尝试update或delete这些行时会被阻塞,直到当前事务提交或回滚。

FOR UPDATE在Oracle中到底锁什么
Oracle的FOR UPDATE只锁定查询返回的行,且仅对当前事务可见——其他会话尝试UPDATE或DELETE这些行时会被阻塞,直到你提交或回滚。它不锁表、不锁索引、也不锁未查出的行。如果你用SELECT * FROM orders WHERE status = 'PENDING' FOR UPDATE,只有满足条件的那些行被锁,哪怕status字段没建索引,Oracle仍能精确定位并加行级锁(通过rowid)。
常见错误:WHERE条件没走索引导致锁升级风险
当WHERE条件无法利用索引时,Oracle可能执行全表扫描,虽然仍只锁命中的行,但扫描过程会短暂持有大量缓冲区闩(latch),并发高时容易引发争用。更隐蔽的问题是:如果后续SQL不小心漏写WHERE(比如误写成UPDATE orders SET ...无条件),就会因行锁存在而卡住——不是语法错,而是被自己之前FOR UPDATE的事务堵住了。
- 务必确认关键
WHERE字段有合适索引,用EXPLAIN PLAN验证是否走了索引访问路径 - 避免在
FOR UPDATE后长时间停留(比如用户输入等待),锁持有时间越长,阻塞窗口越大 - 不要在PL/SQL循环里反复
SELECT ... FOR UPDATE同一组数据,考虑一次性查出+批量处理
SKIP LOCKED:解决队列类场景的死锁和饥饿
多个进程竞争处理同一批待办记录(如任务队列)时,不用SKIP LOCKED会导致所有进程在第一条被锁行上排队,实际只有一人能进,其余干等。加上它以后,每个SELECT ... FOR UPDATE SKIP LOCKED会跳过已被其他事务锁住的行,直接取下一个可用行。
SELECT id, payload FROM tasks WHERE status = 'READY' ORDER BY created_at FETCH FIRST 1 ROW ONLY FOR UPDATE SKIP LOCKED;
注意:SKIP LOCKED是Oracle 12c引入的,11g及更早版本不支持;它不能和FOR UPDATE NOWAIT共存;且ORDER BY + FETCH必须配合使用才能稳定取到“最早就绪”的一条——否则可能因并发修改导致排序结果漂移。
NOWAIT与WAIT的区别和超时陷阱
FOR UPDATE NOWAIT在发现目标行已被锁时立刻报错ORA-00054: resource busy,适合需要快速失败重试的场景;而FOR UPDATE WAIT 5会最多等5秒,超时后同样报ORA-00054。但很多人忽略一点:这个等待时间是**针对单次语句执行**的,不是整个事务。如果你在循环里反复执行带WAIT 5的查询,每次都会重新计时5秒,可能造成意外延迟。
- 业务逻辑明确允许跳过时,优先用
SKIP LOCKED而非WAIT - 用
NOWAIT时,必须捕获ORA-00054并做合理降级(比如记录日志、稍后重试),不能让异常穿透到应用层暴露给用户 -
WAIT n的n最大为1000000000秒(约31年),但设太大等于放弃控制权,建议严格限制在秒级
真正难处理的从来不是怎么加锁,而是锁住之后做什么——更新逻辑是否幂等、失败后如何释放资源、下游系统是否感知到锁等待,这些往往比FOR UPDATE本身更消耗调试时间。











