SELECT FOR UPDATE NOWAIT 会立即报错而非等待锁,适用于高并发抢资源场景;它仅作用于行级锁,要求MySQL 8.0.1+,需应用层捕获错误3572并妥善处理。

SELECT FOR UPDATE NOWAIT 立即失败,不等锁
当业务中存在高并发抢资源(如库存扣减、订单分配)场景时,SELECT ... FOR UPDATE 默认会阻塞等待,直到锁释放或超时(默认 50 秒),导致连接堆积、响应延迟甚至雪崩。NOWAIT 就是为解决这个而生:它让语句在无法立即获取行锁时**立刻报错退出**,而不是卡住。
常见错误现象:ERROR 3572 (HY000): Statement aborted because lock(s) could not be acquired immediately and NOWAIT is set. 这不是异常,而是预期行为——说明你成功避开了等待。
-
NOWAIT只作用于行级锁(InnoDB 的记录锁、间隙锁、临键锁),对表级锁(如LOCK TABLES)无效 - 必须配合
FOR UPDATE或FOR SHARE使用,单独写NOWAIT语法错误 - MySQL 8.0.1+ 才支持,低版本会直接报
Unknown syntax - 应用层需捕获该错误码(3572),并做重试、降级或返回友好提示,不能当作崩溃处理
NOWAIT 和 innodb_lock_wait_timeout 的关系
innodb_lock_wait_timeout 是全局/会话级的“最大等待时间”,而 NOWAIT 是单条语句级的“零等待”。两者互斥:一旦用了 NOWAIT,innodb_lock_wait_timeout 就完全不起作用。
典型误用场景:有人想“先等 2 秒,不行再放弃”,但 NOWAIT 不支持这种折中。它只有两个状态:拿到锁就执行,拿不到锁就立刻失败。真要实现带超时的尝试,得靠应用层循环 + SLEEP + 重试逻辑,或改用 SKIP LOCKED 配合业务兜底。
- 全局设
SET GLOBAL innodb_lock_wait_timeout = 2影响所有未显式加NOWAIT的语句,但对已有连接无效(需新连接) - 会话级设
SET SESSION innodb_lock_wait_timeout = 2只影响当前连接后续的普通FOR UPDATE,不影响已加NOWAIT的语句 -
NOWAIT的响应速度是毫秒级的,比调小innodb_lock_wait_timeout更彻底、更可控
实战中容易忽略的锁粒度问题
NOWAIT 失败不等于“没查到数据”,而极可能是锁落在了**非目标行但被扫描到的索引记录上**。比如查询条件没走索引,触发全表扫描,就会对所有扫描过的行加意向锁;哪怕你只想要 id = 100,也可能因扫描了 1 万行而被其中任意一行的锁挡住。
所以 NOWAIT 报错后,第一反应不该是“重试”,而是检查执行计划:
- 用
EXPLAIN SELECT ... FOR UPDATE NOWAIT确认是否走了索引,是否出现type: ALL或Extra: Using where - 确保
WHERE条件列上有合适索引(主键、唯一键、覆盖索引优先) - 避免在
FOR UPDATE中使用函数、隐式类型转换、OR条件等破坏索引使用的写法 - 注意间隙锁影响:即使查询条件精确匹配,若范围扫描(如
WHERE price BETWEEN 10 AND 20),也会锁住间隙,NOWAIT同样会因此失败
和 SKIP LOCKED 的关键区别在哪
NOWAIT 和 SKIP LOCKED 都是 MySQL 8.0 引入的非阻塞锁控制手段,但语义完全不同:
-
NOWAIT:整条语句失败,**不返回任何行**,适合“强一致性+快速失败”场景(如秒杀下单,要么立刻抢到,要么明确告知售罄) -
SKIP LOCKED:跳过被锁的行,**只返回能加锁的行**,适合“尽力而为+吞吐优先”场景(如任务队列分发,跳过正在处理的任务,取下一个可用任务) - 两者不可混用:
FOR UPDATE NOWAIT SKIP LOCKED语法非法 -
SKIP LOCKED在某些旧版客户端驱动中可能被忽略(如部分 JDBC 版本),而NOWAIT的报错更明确、更易感知
真正难的从来不是加不加 NOWAIT,而是判断哪条语句该加、加了之后应用逻辑是否健壮、失败路径有没有被完整覆盖——很多线上问题,其实出在 catch (SQLException e) { if (e.getSQLState().equals("HY000") && e.getErrorCode() == 3572) { /* 忽略?重试?还是直接返回? */ } } 这一段没写对。











