SELECT FOR UPDATE NOWAIT 不会卡住,而是立即报 ORA-00054 错误;其作用是放弃等待、暴露锁冲突,将控制权交还应用层处理。
SELECT FOR UPDATE NOWAIT 会卡住还是报错?
它不会卡住,而是立刻报错 ora-00054: resource busy and acquire with nowait specified。这是 nowait 的核心作用:放弃等待,暴露锁冲突,把控制权交还给应用层处理。
常见错误现象是程序突然抛出这个异常后直接崩溃——因为没预期到数据库会“不等就炸”,也没做重试或降级逻辑。
- 只在 Oracle 里有效;MySQL 的
SELECT ... FOR UPDATE NOWAIT是 8.0.1 才支持,旧版本会忽略NOWAIT - PostgreSQL 没有
NOWAIT,得用SELECT ... FOR UPDATE SKIP LOCKED或显式检查pg_try_advisory_lock() - 如果事务里先执行了普通
SELECT,再执行SELECT ... FOR UPDATE NOWAIT,Oracle 仍可能阻塞——锁是在真正执行 FOR UPDATE 时才申请的,前面的 SELECT 不占锁
FOR UPDATE 锁的是行还是整张表?
默认锁行,但前提是 WHERE 条件能走索引。一旦走不上索引(比如对无索引字段查询、或用了函数/隐式转换),Oracle 可能升级为表级锁,导致整个表被堵住。
典型场景:更新用户余额前先查当前值,写成 SELECT balance FROM users WHERE name = ? FOR UPDATE NOWAIT,但 name 没建索引 → 全表扫描 → 表锁 → 别人连查其他用户都会被 NOWAIT 拒绝。
- 用
EXPLAIN PLAN确认执行计划是否走了索引扫描(INDEX RANGE SCAN或UNIQUE SCAN) - WHERE 中避免
UPPER(name) = ?这类写法,否则索引失效 - 复合索引要注意最左前缀匹配,
(user_id, status)索引无法支撑WHERE status = ?单独查询
为什么加了 NOWAIT 还出现死锁?
NOWAIT 只解决“等锁”问题,不解决“循环加锁”问题。两个事务按不同顺序更新两行,哪怕都带 NOWAIT,只要其中一个成功拿到第一把锁,另一个在申请第二把锁时就会触发死锁检测,Oracle 主动回滚其中一个并报 ORA-00060。
例如事务 A:先锁 id=100,再锁 id=200;事务 B:先锁 id=200,再锁 id=100。即使都用 NOWAIT,第二个锁申请仍可能撞上对方已持有的锁。
- 所有涉及多行更新的逻辑,必须约定统一的锁顺序(比如永远按
id升序锁定) - 避免在应用层分多次执行
SELECT ... FOR UPDATE NOWAIT,应尽量合并为单条语句一次性锁住所有目标行 - 不要在事务中混用
SELECT FOR UPDATE和SELECT FOR UPDATE NOWAIT,语义不一致容易误判锁状态
并发高时 NOWAIT 报错太多,该怎么兜底?
不是加重试就行。盲目重试可能放大冲突,尤其当锁持有时间长(比如事务里夹着 HTTP 调用)时,重试只会让队列更长。
真正关键的是区分场景:是“抢资源”(如秒杀库存),还是“防覆盖”(如编辑文章)?前者适合用 NOWAIT + 降级提示,后者更适合用乐观锁(version 字段)+ 业务重试。
- 秒杀类:捕获
ORA-00054后直接返回“手慢了”,别重试 - 编辑类:改用
UPDATE ... SET ... WHERE id = ? AND version = ?,失败则查最新版+提示冲突 - 真要重试,必须加指数退避(如 10ms → 30ms → 100ms),且限制总重试次数(最多 3 次)
最容易被忽略的一点:锁的粒度和事务边界必须对齐。锁了一行数据,结果事务里干了 2 秒的文件上传,那这行锁就白白占着资源,别人一碰就 NOWAIT 失败——这不是数据库的问题,是业务逻辑没拆开。










