MySQL 8.0.1+ 原生支持 SKIP LOCKED 和 NOWAIT 语法,需配合 FOR UPDATE/SHARE 使用、命中索引 WHERE 条件及显式 LIMIT,SKIP LOCKED 用于跳过被锁行提升并发,NOWAIT 则快速失败报错3572,二者均非配置项且须在应用层妥善处理语义与一致性。

SKIP LOCKED 和 NOWAIT 不是配置项,而是 SQL 语法层面的选项,MySQL 8.0.1+ 原生支持,无需额外配置参数或开关。真正要做的,是写对 SQL、用对场景、避开常见误用。
SELECT ... FOR UPDATE SKIP LOCKED 怎么写才有效
SKIP LOCKED 只在锁定读(FOR UPDATE 或 FOR SHARE)中生效,且必须作用于可命中索引的 WHERE 条件,否则会退化为全表扫描加锁,结果不可控。
- 必须有明确主键或唯一索引条件,例如
WHERE sku_id = ?,不能是WHERE status = 'pending'(无索引时会锁整张表的扫描路径) -
LIMIT建议显式加上,避免一次捞太多行导致锁范围过大 - 不支持子查询中嵌套使用(外部
FOR UPDATE SKIP LOCKED不会传递到子查询)
示例正确写法:
SELECT * FROM stock WHERE sku_id = 123 AND count > 0 FOR UPDATE SKIP LOCKED LIMIT 1;
错误写法(失效或危险):
-
SELECT * FROM stock WHERE count > 0 FOR UPDATE SKIP LOCKED→ 可能锁住大量无关行 -
SELECT * FROM stock WHERE sku_id IN (123, 456) FOR UPDATE SKIP LOCKED→ MySQL 8.0.22 之前不保证跳过单个被锁的sku_id,行为依赖优化器路径
NOWAIT 报错后怎么处理业务逻辑
NOWAIT 的本质是「快速失败」,它不跳过数据,而是直接抛出 ERROR 3572 (HY000): Do not wait for lock.。这意味着你必须在应用层捕获这个错误,并决定重试、降级或返回库存不足。
- 错误码固定为
3572,不是超时错误(1205)或死锁(1213),应单独判断 - 不建议在高并发下无限制重试,容易引发雪崩;更适合配合限流(如令牌桶)使用
- 若用 ORM(如 MyBatis、Hibernate),需确认其是否透传原生错误码——部分框架会把
3572包装成通用 SQLException,丢失语义
典型处理伪代码:
try {
execute("SELECT * FROM stock WHERE sku_id = ? FOR UPDATE NOWAIT");
updateStock();
} catch (SQLException e) {
if (e.getSQLState().equals("HY000") && e.getErrorCode() == 3572) {
return "sold_out_or_busy";
}
throw e;
}为什么 SKIP LOCKED 在秒杀里常比 NOWAIT 更实用
秒杀核心诉求是「尽可能多地分发库存」,而不是「每条请求都必须成功」。SKIP LOCKED 天然适配这个模型:它让多个并发请求并行扫过同一组候选行,各自取走一个未被锁的记录。
- 它绕过了串行化瓶颈,QPS 随并发线程数近似线性增长(前提是索引高效、无锁争用热点)
- 但要注意:结果集大小不确定,
SELECT ... SKIP LOCKED LIMIT 1可能返回空集,业务必须容忍 - 容易踩的坑是忘记后续
UPDATE的 where 条件二次校验。比如:SELECT id, count FROM stock WHERE sku_id = 123 FOR UPDATE SKIP LOCKED LIMIT 1; -- 假设拿到 id=456, count=1 UPDATE stock SET count = count - 1 WHERE id = 456 AND count > 0; -- ✅ 必须带 count > 0
否则可能因并发导致超卖(两个事务同时读到 count=1,都执行减 1)
SKIP LOCKED 看似简单,但它的效果高度依赖 WHERE 条件是否走索引、事务隔离级别是否为 REPEATABLE READ(默认)、以及应用是否做幂等和二次校验。跳过等待不等于跳过风险——真正的难点从来不在语法,而在如何让「跳过」这件事本身不破坏一致性。


















