MySQL行锁和原子更新

时间:2016-10-07 03:25:20

标签: mysql rowlocking

我正在建立一个穷人的排队系统"使用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

1 个答案:

答案 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)方法更耗时。

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

  1. 有一个通常设置为0的列。
  2. 执行“UPDATE ... SET ColName = UNIQ_ID WHERE ColName = 0 LIMIT 1.这将确保只有1个进程可以更新该行,然后通过SELECT获取它。(UNIQ_ID不是MySQL功能,只是一个变量)
  3. 如果您需要唯一的ID,可以使用带有auto_increment的表。

    您还可以种类通过交易执行此操作。如果你在一个表上启动一个事务,从一个线程运行UPDATE foobar SET LockVar=19 WHERE LockVar=0 LIMIT 1;,并在另一个线程上执行完全相同的操作,第二个线程将在它获取其行之前等待第一个线程提交。这可能最终成为一个完整的表阻止操作。