我正在建立一个穷人的排队系统"使用MySQL。它是包含需要执行的作业的单个表(表名为queue
)。我在多台机器上有几个进程,它们的工作就是调用fetch_next2
sproc来从队列中取出一个项目。
这个程序的重点是确保我们永远不会让2个客户获得相同的工作。我认为通过使用SELECT .. LIMIT 1 FOR UPDATE
将允许我锁定一行,以便我可以确定它仅由1个调用者更新(更新使其不再符合SELECT
的标准用于过滤" READY"要处理的作业。
谁能告诉我自己做错了什么?我刚刚遇到过两个不同进程同样工作的情况,所以我知道它没有正常工作。 :)
CREATE DEFINER=`masteruser`@`%` PROCEDURE `fetch_next2`()
BEGIN
SET @id = (SELECT q.Id FROM queue q WHERE q.State = 'READY' LIMIT 1 FOR UPDATE);
UPDATE queue
SET State = 'PROCESSING', Attempts = Attempts + 1
WHERE Id = @id;
SELECT Id, Payload
FROM queue
WHERE Id = @id;
END
答案 0 :(得分:2)
答案代码:
CREATE DEFINER=`masteruser`@`%` PROCEDURE `fetch_next2`()
BEGIN
SET @id := 0;
UPDATE queue SET State='PROCESSING', Id=(SELECT @id := Id) WHERE State='READY' LIMIT 1;
#You can do an if @id!=0 here
SELECT Id, Payload
FROM queue
WHERE Id = @id;
END
您正在做的问题是操作没有原子分组。您正在使用SELECT ... FOR UPDATE syntax。 Docs称它阻止“在某些事务隔离级别读取数据”。但不是所有级别(我认为)。在第一个SELECT和UPDATE之间,另一个SELECT可以从另一个线程发生。您使用的是MyISAM还是InnoDB? MyISAM可能不支持它。
确保此方法正常运行的最简单方法是lock the table。
[编辑]我在这里描述的方法比在上面的代码中使用Id=(SELECT @id := Id)
方法更耗时。
另一种方法是执行以下操作:
如果您需要唯一的ID,可以使用带有auto_increment的表。
您还可以种类通过交易执行此操作。如果你在一个表上启动一个事务,从一个线程运行UPDATE foobar SET LockVar=19 WHERE LockVar=0 LIMIT 1;
,并在另一个线程上执行完全相同的操作,第二个线程将在它获取其行之前等待第一个线程提交。这可能最终成为一个完整的表阻止操作。