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