数据库事务能否保证仅单个工作节点领取Jobs表中的同一任务?
结论
你写的现有事务无法保证任意任务仅被单个节点领取,确实存在两个节点同时处理同一任务的可能。
原理说明
在MySQL InnoDB等支持MVCC的主流数据库默认的可重复读(RR)隔离级别下:
- 普通
SELECT语句是快照读,不会为查询到的行加任何锁,只会读取事务启动时刻的一致性快照,因此多个并发事务可以同时读到同一条status = 'NEW'的任务记录。 - 只有执行
UPDATE语句时才会为匹配的行加排他行锁,锁会持有到事务提交才释放。
冲突复现路径
- 节点A、节点B几乎同时开启事务,执行普通SELECT,都读到了id=100的NEW状态任务
- 节点A先执行UPDATE,成功对id=100加排他锁,将状态改为
IN_PROCESS后提交事务释放锁 - 节点B随后执行UPDATE,由于你的UPDATE语句仅用
id = 'id'作为匹配条件,会直接将已经被修改为IN_PROCESS的任务再次更新为IN_PROCESS,提交事务后两个节点都会认为自己领取到了该任务,最终导致重复执行。
正确修复方案
你可以任选以下两种方案之一解决问题:
方案1:SELECT加排他锁(悲观锁,适合高并发场景)
将查询语句改为带排他锁的写法,让SELECT阶段就锁住目标行:
BEGIN TRANSACTION; SELECT id FROM jobs WHERE status = 'NEW' ORDER BY created_at LIMIT 1 FOR UPDATE; -- 新增:查询时就加排他锁,其他事务的同语句会被阻塞 -- rollback if id is null UPDATE jobs SET status = 'IN_PROCESS' WHERE id = 'id'; COMMIT;
如果你的数据库支持SKIP LOCKED语法(MySQL 8.0+、PostgreSQL等),还可以在FOR UPDATE后加SKIP LOCKED,让抢不到锁的事务直接跳过已被加锁的行,避免阻塞,提升任务领取效率。
方案2:UPDATE增加状态判断(乐观锁,适合低并发场景)
不需要修改SELECT语句,仅修改UPDATE的匹配条件,通过影响行数判断是否领取成功:
UPDATE jobs SET status = 'IN_PROCESS' WHERE id = 'id' AND status = 'NEW'; -- 新增状态校验,只有还是NEW的任务才会被更新
执行完UPDATE后判断该语句的影响行数:如果返回1说明领取成功,返回0说明该任务已经被其他节点领走,直接回滚事务再重新查询下一个任务即可。
内容的提问来源于stack exchange,提问作者agentwarn
相关产品推荐
相关产品推荐

