SKIP LOCKED 是解决多 Worker 抢任务不卡死的核心方案,通过跳过已锁行实现无等待并发分发,需配合 LIMIT、索引 WHERE、ORDER BY 及及时 UPDATE 状态。

任务队列分发:多个Worker抢任务不卡死
这是 SKIP LOCKED 最典型、最不可替代的场景。比如订单履约、工单分发、消息消费等,多个进程并发执行 SELECT ... FOR UPDATE SKIP LOCKED,各自拿到一批未被锁定的待处理记录,互不阻塞。
关键点在于:它把“抢锁失败→等待→超时→重试”的串行逻辑,变成了“跳过已锁行→立即返回可用行”的并行逻辑。吞吐量直接翻倍,连接池压力大幅下降。
- 必须配合
LIMIT使用,否则可能锁住大量无关行 -
WHERE条件一定要走索引,否则会全表扫描+锁表(InnoDB 退化为表锁) - 查完必须尽快
UPDATE状态(如SET status = 'processing'),否则锁持有时间过长,反而降低并发 - 推荐加
ORDER BY id(主键升序),避免因 MVCC 快照导致某些行长期被跳过(饥饿问题)
秒杀库存预占:跳过已被抢的行,不排队不超时
和 NOWAIT 不同,SKIP LOCKED 不报错,而是静默过滤掉已被其他事务锁定的库存行。适合“能抢就抢,抢不到就换下一个”的柔性秒杀策略。
例如:用户请求“抢一件尺码为 M 的商品”,后端执行:
SELECT id, stock FROM items WHERE sku = 'M' AND stock > 0 ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED;
如果所有 sku = 'M' 的记录都已被锁定,查询返回空;应用层可降级为提示“该尺码暂无库存”,而不是让用户干等 50 秒或收到死锁错误。
- 仅适用于精确匹配或范围较小的
WHERE条件;大范围扫描 +SKIP LOCKED效率低,且可能跳过本应可用的行 - MySQL 在
READ COMMITTED隔离级别下,SKIP LOCKED行为不如REPEATABLE READ稳定,建议统一使用后者 - 不能替代业务层的库存校验——查到的
stock > 0只是快照值,UPDATE 时仍需WHERE stock >= 1做最终判断
定时任务避让:从队列表捞数据时不撞锁
很多定时任务(如每分钟清理过期 token、归档日志)直接 SELECT ... FOR UPDATE,容易在高峰期与业务事务争抢同一张表的行锁。用 SKIP LOCKED 后,任务只处理当前无人争抢的记录,天然实现错峰。
示例语句:
SELECT id FROM task_queue WHERE status = 0 ORDER BY id LIMIT 100 FOR UPDATE SKIP LOCKED;
它不会因为某条记录正被业务更新而卡住,也不会因间隙锁冲突导致整个查询阻塞。
- 务必限制
LIMIT,避免单次事务锁太多行,拖慢整体响应 - 任务执行完要立刻
COMMIT,不要在事务里做耗时操作(如调外部 API、写文件) - 如果任务表有高频插入,建议对
id或created_at建降序索引(MySQL 8.0 支持),加速ORDER BY ... DESC LIMIT
为什么不能用于所有高并发场景?
SKIP LOCKED 不是万能锁优化开关。它只对行级排他锁(FOR UPDATE)生效,对 INSERT ... SELECT、LOCK TABLES、间隙锁(Gap Lock)或临键锁(Next-Key Lock)不起作用。
更关键的是:它改变了语义——你得到的不再是“满足条件的所有行”,而是“当前未被锁定的子集”。如果业务强依赖“必须处理全部待办项”,那它就不适用;此时应考虑分片、限流或改用 NOWAIT + 重试机制。
最容易被忽略的一点:应用层必须主动检查查询结果是否为空。很多人写了 SKIP LOCKED 却忘了加 if (rows.length === 0) { /* 退出或重试 */ },导致任务静默跳过,问题难以排查。


















