NOWAIT 不是避免阻塞的银弹,而是将阻塞转为明确失败;它使 SELECT ... FOR UPDATE 在无法获取行锁时立即报 ERROR 3572,而非等待 innodb_lock_wait_timeout 超时。

NOWAIT 不是“避免阻塞”的银弹,而是把阻塞转为明确失败——它让 SELECT ... FOR UPDATE 在拿不到锁时立刻报错,而不是卡住等超时。
为什么 FOR UPDATE 默认会卡住?
MySQL 8.0 之前,SELECT ... FOR UPDATE 遇到已被加锁的行,会进入 lock wait 状态,直到 innodb_lock_wait_timeout(默认 50 秒)超时才抛出 ERROR 1205 (HY000): Lock wait timeout exceeded。这期间连接一直占用着,容易拖垮连接池或耗尽 max_connections。
常见错误现象:
- 应用日志里反复出现
Lock wait timeout exceeded - 数据库连接数缓慢上涨,
SHOW STATUS LIKE 'Threads_connected'持续高位 - 同一行被多个请求争抢时,后到的请求在 DB 层“静默等待”,监控难发现
SELECT ... FOR UPDATE NOWAIT 的正确写法和限制
它只作用于行级锁(RECORD),对表锁、意向锁、间隙锁(GAP)或 next-key 锁无效;且必须配合 FOR UPDATE 或 FOR SHARE 使用,不能单独存在。
实操要点:
- 语法必须紧贴
FOR UPDATE后:SELECT * FROM t WHERE id = 1 FOR UPDATE NOWAIT,写成FOR UPDATE ... NOWAIT中间有换行或注释可能报错 - 错误码固定为
ERROR 3572 (HY000),不是 1205,应用层需专门捕获该码做重试或降级 - DDL 操作也有
NO_WAIT(注意拼写不同),但那是针对 MDL 锁的,和这里的NOWAIT无关,混用会语法报错 - 不支持在
UNION、子查询或视图中使用NOWAIT,否则提示Unsupported use of FOR UPDATE NOWAIT
典型场景:秒杀库存扣减中的非阻塞处理
假设订单表 orders 有 status 字段,要从 pending 状态中选一条未被锁定的记录扣减:
SELECT id, stock FROM orders WHERE status = 'pending' AND stock > 0 ORDER BY id LIMIT 1 FOR UPDATE NOWAIT;
如果该行正被另一个事务持有 X 锁,这条语句立即返回 ERROR 3572,应用可立刻尝试下一条(比如改用 SKIP LOCKED),而不是傻等。
性能影响注意点:
- 不会降低单次锁获取开销,但显著减少连接等待时间与资源堆积
- 高并发下错误率上升,需配套熔断、重试退避(如指数退避)或 fallback 到异步队列
- 不能替代业务层幂等设计——
NOWAIT失败后重试仍需防重复扣减
容易被忽略的关键细节
NOWAIT 生效的前提是事务已开启(BEGIN 或自动提交关闭),且隔离级别至少为 READ COMMITTED 或更高;在 AUTOCOMMIT=1 下执行 FOR UPDATE NOWAIT,语句本身会隐式开启事务,但结束后立即提交,锁也随即释放——这意味着你几乎拿不到锁,也起不到保护作用。
真正需要它的地方,一定是显式事务 + 行锁竞争密集的短生命周期操作。别把它当成通用“提速开关”,它解决的是“等死”问题,不是“慢”问题。











