FOR UPDATE NOWAIT立刻报错是因为彻底取消等待而非缩短超时:只要目标行被其他事务加锁,就立即抛出ERROR 3572;它必须配合FOR UPDATE/SHARE在显式事务中使用,仅MySQL 8.0.1+支持,且仅作用于行锁。

FOR UPDATE NOWAIT 为什么会立刻报错而不是卡住
它不是把等待时间调短,而是彻底取消等待:只要目标行已被其他事务加了 FOR UPDATE 或 FOR SHARE 锁,当前语句就立刻终止,抛出 ERROR 3572 (HY000)。这和调小 innodb_lock_wait_timeout 有本质区别——后者仍是阻塞,只是等得短些;NOWAIT 是非阻塞设计。
常见错误现象包括:Lock wait timeout exceeded 在日志里反复出现、SHOW STATUS LIKE 'Threads_connected' 持续高位、同一行被争抢时后到请求在 DB 层“静默等待”难以监控。
NOWAIT 的语法和生效前提有哪些硬性要求
NOWAIT 只对显式锁定读生效,且必须满足以下全部条件:
-
SELECT语句中必须明确包含FOR UPDATE或FOR SHARE,NOWAIT必须紧贴其后(不能换行、不能有注释隔开) - 必须在显式事务中执行:
START TRANSACTION或SET autocommit = 0;若autocommit = 1,语句会隐式开启并立即提交事务,锁无法延续到后续UPDATE - 仅支持 MySQL 8.0.1+,低版本直接报
Unknown syntax - 不支持子查询、视图、
UNION、UPDATE或DELETE语句——下面这些都无效:SELECT * FROM t WHERE id IN (SELECT id FROM t2 FOR UPDATE NOWAIT)、UPDATE t SET x=1 NOWAIT、SELECT * FROM t NOWAIT
报错 ERROR 3572 后该怎么做
这个错误不是异常,而是你设计的“快速失败路径”被触发了。应用层必须专门捕获它,而不是泛化处理所有 SQL 错误:
- 捕获条件应为:
SQLSTATE = 'HY000'且errno = 3572,不能只抓1205(那是超时错误) - 典型 fallback 策略包括:改用
SKIP LOCKED再查一次、返回“资源已被占用”友好提示、将请求写入异步队列重试、或走降级逻辑(如跳过扣减、标记待人工审核) - 切忌一遇到
3572就重试原语句——若没做限流或退避,高并发下可能瞬间打满连接池
为什么加了 NOWAIT 还是报错?先看执行计划
NOWAIT 失败不等于“没查到数据”,极可能是锁落在了**非目标行但被扫描到的索引记录上**。比如 WHERE 条件没走索引,触发全表扫描,就会对所有扫描行加意向锁;哪怕你只想要 id = 100,也可能因扫了 1 万行而被其中任意一行的锁挡住。
实操建议:
- 用
EXPLAIN SELECT ... FOR UPDATE NOWAIT确认是否走了索引,警惕type: ALL或Extra: Using where - 确保
WHERE条件列上有合适索引(主键、唯一键、覆盖索引优先) -
NOWAIT只作用于行锁(record lock),对间隙锁(gap lock)或 next-key 锁无影响;若因 gap lock 导致无法插入/更新,NOWAIT不起作用
真正容易被忽略的是锁粒度问题:你以为只锁了一行,其实扫描路径上所有索引记录都可能被加锁。执行计划不干净,NOWAIT 就成了“伪非阻塞”。


















