SELECT ... FOR UPDATE只锁查询结果集中的行,事务提交或回滚后释放;不阻塞读操作和未加锁的更新;需配合索引、显式事务和执行计划验证确保锁粒度精准。
PL/SQL里SELECT ... FOR UPDATE到底锁什么?
它只锁查询结果集中的行,不是整张表,也不是执行语句的会话本身。锁在事务提交或回滚后才释放,不是语句执行完就松开。
常见错误是以为加了FOR UPDATE就能防止所有并发修改——其实它不阻止其他会话读(SELECT),也不阻止没加锁的更新(比如直接UPDATE t SET x=1 WHERE id=5),只让其他会话在尝试对同一行加锁时阻塞或报错。
- 必须在事务内使用,单独执行后不提交,锁一直挂着
- 如果查询没走索引、返回大量行,锁粒度大,容易引发争用
-
FOR UPDATE OF col_name只是语义提示,实际锁的仍是整行,不是单列
SELECT ... FOR UPDATE NOWAIT报ORA-00054: resource busy怎么处理?
这不是异常,是预期行为:说明目标行正被另一个未提交事务持有锁。不能靠重试掩盖,得设计应对逻辑。
典型场景是抢号、库存扣减、订单生成等需要“要么立刻拿到,要么放弃”的操作。盲目捕获ORA-00054然后sleep再试,反而可能拖垮系统。
- 应用层应明确区分“业务不可用”和“暂时冲突”,前者返回用户友好提示(如“已被他人抢先下单”)
- 避免在存储过程中隐式重试——PL/SQL里用
EXCEPTION WHEN OTHERS THEN兜底重试,容易掩盖真正错误 - 真要重试,建议用带退避的客户端逻辑(如指数退避),且设置最大重试次数
为什么FOR UPDATE SKIP LOCKED比NOWAIT更适合队列消费?
它不报错,而是跳过被锁的行,返回当前可用的行集合。适合多消费者并发拉取任务,天然避免争抢同一行。
比如消息队列表、待处理工单表,多个工作进程同时执行SELECT ... FOR UPDATE SKIP LOCKED,各自拿到不同行,互不阻塞。
-
SKIP LOCKED是Oracle 11gR2+才支持,低版本别用 - 不能和
NOWAIT混用,语法冲突 - 注意配合
ROWNUM或FETCH FIRST限制返回条数,否则可能一次扫太多行影响性能 - 如果业务要求“严格顺序处理”,它反而不合适——因为跳过的行下次可能被别的进程拿走
用FOR UPDATE时最容易被忽略的三个点
一是自动提交陷阱:AUTOCOMMIT开启时(如SQL*Plus默认),每条语句结束即提交,锁瞬间释放,根本起不到保护作用;二是游标生命周期:在PL/SQL中用OPEN cur FOR SELECT ... FOR UPDATE,锁直到CLOSE或事务结束才释放,不是FETCH完就松;三是隐式类型转换导致索引失效——本想锁某ID,却因传入字符串触发全表扫描,锁住几百行,别人一查就卡住。
- 检查会话
AUTOCOMMIT状态:SHOW AUTOCOMMIT(SQL*Plus)或看应用连接配置 - 显式控制事务边界,尤其在存储过程中避免依赖隐式提交
- 执行前用
EXPLAIN PLAN确认执行计划是否走了预期索引
FOR UPDATE别急着测功能,先看执行计划和锁视图V$LOCK。










