select for update只在显式事务中有效,autocommit开启时锁立即释放;必须确保where条件走索引、update与select定位同一行、合理选用nowait或skip locked,并避免隐式转换与长事务持锁。

FOR UPDATE 不是“加个关键字就安全”,它只在事务边界内有效,且锁行为高度依赖 WHERE 条件、索引和客户端设置。直接写 SELECT ... FOR UPDATE 后跟 UPDATE,90% 的情况会失效或锁错行。
为什么刚执行完 SELECT FOR UPDATE 就发现 UPDATE 没锁住?
根本原因是事务没控制住——Oracle 默认每条语句自动提交(AUTOCOMMIT=ON),SELECT ... FOR UPDATE 执行完立刻释放锁,后续 UPDATE 是全新事务,完全不受保护。
- SQL*Plus / SQL Developer 中必须先执行
SET AUTOCOMMIT OFF - JDBC 中必须调用
connection.setAutoCommit(false),再executeQuery("SELECT ... FOR UPDATE"),最后executeUpdate("UPDATE ..."),统一commit() - PL/SQL 存储过程中,
OPEN cur FOR SELECT ... FOR UPDATE后,锁持续到CLOSE cur或事务结束,不是FETCH完就松 - Spring 的
@Transactional方法里,SELECT FOR UPDATE必须和UPDATE在同一个方法内,且不能被 AOP 代理切断(如 self-invocation)
WHERE 条件不一致导致锁失效或误更新
很多人写 SELECT id, name FROM orders WHERE status = 'PENDING' FOR UPDATE,然后执行 UPDATE orders SET status = 'PROCESSED' WHERE id = ? —— 这看似合理,但风险极大:如果 id 不是主键或唯一索引列,或 WHERE 逻辑未覆盖原始约束,数据库无法确认你操作的是同一组行。
-
SELECT中必须包含足够定位唯一行的列(强烈建议主键),例如SELECT order_id, status FROM orders WHERE status = 'PENDING' AND order_id IN (SELECT order_id FROM ...) -
UPDATE的WHERE必须精确匹配查出的主键值,如UPDATE orders SET status = 'PROCESSED' WHERE order_id IN (1001, 1002) - 避免在
WHERE中混用类型:WHERE id = '123'(字符串)可能触发隐式转换,导致全表扫描+锁升级,实际锁住几百行
NOWAIT 和 SKIP LOCKED 到底该选哪个?
NOWAIT 不是“失败重试”的快捷键,它是明确拒绝等待的信号;SKIP LOCKED 是并发消费场景的真正解法,但有版本和语义限制。
-
SELECT ... FOR UPDATE NOWAIT遇到冲突直接报ORA-00054,适合“强抢占”场景(如抢号、秒杀),应用层应立即返回用户提示(如“已被他人下单”),而非盲目 sleep + retry -
SELECT ... FOR UPDATE SKIP LOCKED只跳过已被锁的行,返回当前可用行集合,适合消息队列、工单分发等多消费者并发拉取场景(Oracle 11gR2+ 支持) - 不能同时用
NOWAIT和SKIP LOCKED,语法冲突;也不建议对大结果集无限制使用,应配合ROWNUM 或 <code>FETCH FIRST 10 ROWS ONLY - 若业务要求“严格顺序处理”,
SKIP LOCKED反而不合适——跳过的行下次可能被别的进程拿走
最容易被忽略的锁粒度陷阱
你以为只锁了一行,其实锁了一片;你以为锁住了数据,其实锁住了索引块。
- 查询没走索引(如
WHERE name LIKE '%abc%')、或用了非唯一索引字段(如WHERE status = 'PENDING'),可能触发锁升级,在 RC 隔离级别下锁住整个索引范围 -
FOR UPDATE OF col_name只是语义提示,实际锁的仍是整行,不是单列;想锁单列需靠应用逻辑或触发器模拟 - 隐式类型转换、函数包裹字段(如
WHERE UPPER(name) = 'JOHN')、绑定变量窥探失效,都可能导致执行计划突变,锁范围暴增 - 长时间持有锁(如查完后做耗时计算再更新)是最大性能杀手,锁持有时间应压缩到毫秒级
真正难的从来不是写对那条 SELECT ... FOR UPDATE,而是确保它在整个事务生命周期中始终精准作用于目标行——这要求你同时理解执行计划、事务边界、客户端配置和业务语义。漏掉任意一环,锁就形同虚设。











