nowait立即报错error 3572而非等待,本质是取消等待而非缩短超时;必须配合for update/share在显式事务中使用,仅mysql 8.0.1+支持,且仅作用于行锁。

NOWAIT 一加锁就报错,不是超时而是彻底不等
MySQL 8.0 的 NOWAIT 不是把等待时间调短,而是直接取消等待——只要目标行已被其他事务用 FOR UPDATE 或 FOR SHARE 加锁,当前语句立刻失败,返回固定错误 ERROR 3572 (HY000)。这和 innodb_lock_wait_timeout 完全不同:后者仍是阻塞式,只是等得少一点。
常见误用包括:
-
SELECT * FROM t WHERE id = 123 NOWAIT—— 缺少FOR UPDATE或FOR SHARE,NOWAIT被忽略(语法合法但无效) - 在子查询里写
SELECT id FROM t FOR UPDATE NOWAIT,外层没加锁,整个语句不触发行锁,NOWAIT不起作用 - 没显式开启事务(
START TRANSACTION或SET autocommit = 0),NOWAIT行为退化为普通非锁定读,不报错也不加锁
SKIP LOCKED 跳过被锁行,结果集自动变小
SKIP LOCKED 只对 SELECT ... FOR UPDATE 或 SELECT ... FOR SHARE 生效,它的作用是:扫描过程中遇到已被加锁的行,直接跳过,不等待、不报错、不阻塞,最终返回的结果集中不包含这些行。
典型适用场景是任务队列分发或库存预占:
-
SELECT * FROM tasks WHERE status = 'pending' ORDER BY id LIMIT 10 FOR UPDATE SKIP LOCKED—— 多个消费者并发执行,各自拿到互不重叠的待处理任务 -
SELECT * FROM stock WHERE sku_id IN (123, 456) FOR UPDATE SKIP LOCKED—— MySQL 8.0.22 之前行为不稳定,建议升级后使用;若只查单个主键,效果更可靠
注意:SKIP LOCKED 对间隙锁(gap lock)无效,它只跳过已被加 record lock 的行。
为什么 NOWAIT 报错后不能盲目重试
ERROR 3572 并不等于“数据不存在”或“条件不匹配”,极可能是因为查询扫描了大量非目标行并被其中任意一行的锁挡住。比如:
- WHERE 条件没走索引,导致全表扫描,每行都尝试加意向锁,任一被锁就触发失败
- 用了范围查询但没覆盖索引,扫描到中间某行正被其他事务锁定
所以出错第一反应不是重试,而是检查执行计划:
- 用
EXPLAIN SELECT ... FOR UPDATE NOWAIT看type是否为ALL,Extra是否含Using where - 确保查询字段有合适索引:主键 > 唯一键 > 覆盖索引;避免在高并发路径上依赖函数索引或前缀索引
ORM 框架里容易丢掉 NOWAIT 的语义
MyBatis、Hibernate 等框架默认会把原生错误码包装成通用 SQLException,导致 ERROR 3572 被吞掉,业务层无法区分是锁冲突还是其他 SQL 错误。
应对方式包括:
- 在 MyBatis 中启用
jdbc4驱动并捕获SQLState或原生错误码(getErrorCode()== 3572) - Hibernate 6+ 支持
LockMode.PESSIMISTIC_WRITE_NO_WAIT,但需确认方言是否透传 - 绕过 ORM,用原生 JDBC 执行关键语句,自己控制异常分支
真正难的从来不是加 NOWAIT 这个词,而是让整个链路——从 SQL 生成、驱动解析、框架拦截到业务 fallback——都认得清这个错误意味着什么。











