2

我正在使用 MySQL 构建一个“穷人的排队系统”。它是一个包含需要执行的作业的表(表名为queue)。我在多台机器上有几个进程,它们的工作是调用fetch_next2sproc 以从队列中取出一个项目。

这个过程的重点是确保我们永远不会让 2 个客户得到相同的工作。我认为通过使用SELECT .. LIMIT 1 FOR UPDATE将允许我锁定单行,以便我可以确定它仅由 1 个调用者更新(更新后它不再符合SELECT用于过滤“就绪”作业的标准进行处理)。

谁能告诉我我做错了什么?我只是有一些实例,将相同的工作分配给 2 个不同的进程,所以我知道它不能正常工作。:)

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
4

1 回答 1

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 语法。文档说它阻止“读取某些事务隔离级别的数据”。但不是所有级别(我认为)。在您的第一个 SELECT 和 UPDATE 之间,另一个 SELECT 可以从另一个线程发生。您使用的是 MyISAM 还是 InnoDB?MyISAM 可能不支持它。

确保它正常工作的最简单方法是锁定表


Id=(SELECT @id := Id)[编辑] 我在这里描述的方法比使用上面代码中的方法更耗时。

另一种方法是执行以下操作:

  1. 有一列通常设置为 0。
  2. 执行“UPDATE ... SET ColName=UNIQ_ID WHERE ColName=0 LIMIT 1。这将确保只有 1 个进程可以更新该行,然后通过 SELECT 获取它。(UNIQ_ID 不是 MySQL 功能,只是一个变量)

如果你需要一个唯一的 ID,你可以使用一个带有 auto_increment 的表。


你也可以通过交易做到这一点。如果您在表上启动事务,UPDATE foobar SET LockVar=19 WHERE LockVar=0 LIMIT 1;从一个线程运行,并在另一个线程上执行完全相同的操作,则第二个线程将等待第一个线程提交,然后再获得其行。不过,这可能最终成为一个完整的表阻塞操作。

于 2016-10-11T02:03:28.423 回答