FOR UPDATE NOWAIT 不提升单次查询效率,而是将不可控的锁等待转为可捕获的快速失败;默认 FOR UPDATE 会静默挂起50秒,NOWAIT 则毫秒级返回错误3572,需显式事务、精准索引和专门捕获。

FOR UPDATE NOWAIT 不提高单次查询效率,而是把“等锁”这个不可控延迟,变成可捕获、可调度的快速失败。
为什么默认 FOR UPDATE 会拖垮高并发
MySQL 默认的 SELECT ... FOR UPDATE 遇到已被加锁的行时,会卡在 innodb_lock_wait_timeout(默认 50 秒)里等待。这期间连接不释放、事务不结束、线程被占住——不是慢,是“静默挂起”。常见表现包括:
- 应用日志反复出现
ERROR 1205 (HY000): Lock wait timeout exceeded -
SHOW STATUS LIKE 'Threads_connected'持续攀升,接近max_connections - 监控看不到明显慢 SQL,但整体 P99 响应时间突然毛刺上扬
NOWAIT 如何让失败变得“可控”
加了 NOWAIT 后,语句不再等待,而是在毫秒级内返回固定错误:ERROR 3572 (HY000): Statement aborted because lock(s) could not be acquired immediately and NOWAIT is set。这不是异常,是设计行为。关键点在于:
- 必须显式开启事务(
START TRANSACTION或SET autocommit = 0),否则NOWAIT无效且不报错,退化为普通读 - 只对行级锁生效;若 WHERE 条件没走索引导致全表扫描,实际会锁大量无关行,
NOWAIT依然会因其中任意一行被锁而立刻失败 - 应用层必须专门捕获错误码
3572,不能泛化处理所有 SQL 异常——比如和死锁ERROR 1213或主键冲突混为一谈 - 不能和
SKIP LOCKED共用,语法直接报错
NOWAIT 和 SKIP LOCKED 到底怎么选
两者都解决“锁竞争”,但语义完全不同:
-
SELECT ... FOR UPDATE NOWAIT:我要这一行,拿不到就立刻放弃 → 适合“抢唯一资源”,如生成单号、扣减指定商品库存、抢占任务 ID -
SELECT ... FOR UPDATE SKIP LOCKED:我要一条可用的行,跳过被锁的 → 适合“消费队列”,如任务分发、订单批量处理、消息拉取 - 误用典型:用
NOWAIT去查未加索引的status = 'pending',结果每次全表扫描都撞上锁,100% 报错;正确做法是先建索引,再改用精准条件或换SKIP LOCKED
最容易被忽略的底层细节
NOWAIT 失败不等于“数据不存在”或“条件不匹配”,极大概率是锁粒度问题:
- 用
EXPLAIN看执行计划,确认type是const/ref,而非ALL或index;Extra不能含Using where(说明走了全索引扫描) - 检查是否命中主键、唯一索引或覆盖索引;非唯一二级索引 +
FOR UPDATE可能触发间隙锁,而NOWAIT对间隙锁无效 - DDL 操作(如
ALTER TABLE)持有的 MDL 锁不受NOWAIT影响,此时语句仍会等 MDL 锁,错误码也不是3572
真正落地时,NOWAIT 的价值不在语法本身,而在它迫使你正视锁范围、索引质量、事务边界这些长期被掩盖的细节——报错不可怕,可怕的是不知道为什么报错。


















