MySQL 多个消费者怎么用 SKIP LOCKED 领取任务
来源:17golang原创
时间:2026-09-06 08:38:43 184浏览 收藏
多个消费者共同读取 MySQL 任务表时,核心写法是把领取动作放进短事务:先用 SELECT ... FOR UPDATE SKIP LOCKED 锁定一条待处理任务,再在同一事务里把它改成 processing,最后提交。其他消费者遇到这条已锁记录会直接跳过,继续尝试下一条,而不是排队等待。
SKIP LOCKED适合队列式表,不适合要求完整一致视图的普通查询。- 领取和状态更新必须在同一个 InnoDB 事务中,提交后再执行耗时业务。
- 没有任务、消费者崩溃、复制模式和长任务超时,都要有明确的恢复策略。
先把任务表设计成可并发领取
先准备一个状态清晰、能快速筛选的队列表。下面的字段足够演示领取路径,生产环境还可以增加重试次数、租约截止时间和错误摘要。
CREATE TABLE task_queue (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
payload JSON NOT NULL,
status ENUM('pending', 'processing', 'done', 'failed') NOT NULL DEFAULT 'pending',
claimed_by VARCHAR(64) NULL,
claimed_at DATETIME NULL,
PRIMARY KEY (id),
KEY idx_queue_status_id (status, id) -- 让领取条件和排序共享索引
) ENGINE = InnoDB;
status, id 联合索引对应 WHERE status = 'pending' ORDER BY id。它不是为了保证绝对公平,而是让消费者有稳定的扫描顺序;被锁住的旧任务会被跳过,后面的任务可以先被领取。
| 字段或条件 | 作用 | 领取时的判断 |
|---|---|---|
status | 表达任务生命周期 | 只选择 pending |
id | 稳定排序和唯一定位 | 领取后用主键更新 |
claimed_by | 记录消费者身份 | 便于审计与排障 |
claimed_at | 记录租约或超时起点 | 恢复卡住任务时使用 |

在短事务里用 FOR UPDATE SKIP LOCKED 抢一条
每个消费者都要使用自己的数据库连接,并显式开启事务。锁定读只有在事务未提交时才有意义;如果开启自动提交,锁可能在语句结束时就释放,领取和标记就无法形成一个原子动作。
START TRANSACTION;
-- 只找待处理且当前没有被其他消费者锁住的任务
SELECT id, payload
FROM task_queue
WHERE status = 'pending'
ORDER BY id
LIMIT 1
FOR UPDATE SKIP LOCKED;
-- 应用拿到 id 后,在同一事务内完成状态转移
UPDATE task_queue
SET status = 'processing',
claimed_by = 'worker-a',
claimed_at = NOW()
WHERE id = 12345
AND status = 'pending'; -- 防止状态已经变化时误更新
COMMIT; -- 提交后才释放领取锁
应用层要检查查询是否返回了行,以及 UPDATE 的影响行数。查询没有结果时说明当前没有可领取任务,可以短暂退避后重试;不要把它当成异常死循环。两个消费者同时执行时,已被第一个事务锁住的记录会从第二个结果集中消失,但第二个消费者仍可能读到更靠后的任务。
MySQL 官方说明了这个语义:SKIP LOCKED 不等待行锁,直接从结果集中移除被锁行;因此返回的是不一致视图,只适合队列一类允许跳过锁行的场景。它只对行级锁生效,使用语句复制时还要单独评估安全性。
把数据库领取和业务执行分开
不要在事务里调用远程 API、生成文件或执行几分钟的业务。正确边界是“短事务只负责认领,提交后再处理”:任务进入 processing 后,消费者可以在事务外完成工作,成功后更新为 done,失败则写入 failed 或增加重试次数。
进程在提交前崩溃,事务回滚,任务仍是 pending;进程在提交后、业务完成前崩溃,任务可能停留在 processing。后一种情况不能依赖锁自动解决,应结合 claimed_at 做租约超时恢复,并让业务操作具备幂等性。否则恢复消费者可能再次执行已经完成一半的外部动作。

上线前检查这几项并发边界
- 确认表使用 InnoDB,查询条件包含明确的
ORDER BY和LIMIT,并检查(status, id)索引。 - 确认每次领取都显式提交或回滚;连接归还连接池前不能留下未结束事务。
- 为空队列设置退避,死锁或瞬时连接错误采用有限次数重试。
- 为
processing任务保存领取者和时间,定时恢复超时租约。 - 把外部副作用设计成幂等,并根据部署环境评估复制方式和锁等待监控。
常见问题
SKIP LOCKED 会保证任务严格按 id 顺序处理吗?
不会。它只提供“遇到已锁行就跳过”的并发行为,后面的任务可能先完成。需要严格顺序时,应改用单消费者、分区队列或业务层排序。
查询不到任务时应该回滚吗?
如果已经开启事务,建议回滚或提交后再退避,确保连接没有留下未结束事务。空结果本身不是数据库错误。
为什么任务状态已经是 processing,却仍然重复执行?
通常是消费者在提交领取后崩溃,恢复逻辑又重新拿走了超时任务。需要租约、尝试次数和幂等键一起控制,而不是只依赖状态字段。
可以用普通 SELECT 再 UPDATE 代替吗?
不建议。普通读取和更新之间存在竞争窗口,多个消费者可能读到同一行;要么使用锁定读,要么采用带条件的原子更新并设计清晰的重试结果。
因此,MySQL 多消费者队列的最小可靠组合是:InnoDB、短事务、FOR UPDATE SKIP LOCKED、同事务状态更新,以及事务外的幂等执行和超时恢复。
-
374 收藏
-
499 收藏
-
384 收藏
-
184 收藏
-
265 收藏
-
297 收藏
-
384 收藏
-
183 收藏
-
207 收藏
-
477 收藏
-
413 收藏
-
177 收藏
-
490 收藏
-
176 收藏
-
423 收藏
-
405 收藏
-
463 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习