mysql行锁和原子更新

a8jjtwal  于 2021-06-25  发布在  Mysql
关注(0)|答案(1)|浏览(423)

我正在使用mysql构建一个“穷人排队系统”。它是一个包含需要执行的作业的表(表名为 queue ). 我在多台机器上有几个进程,它们的任务是调用 fetch_next2 存储过程以从队列中获取项目。
这个过程的重点是确保我们永远不会让两个客户得到相同的工作。我想通过使用 SELECT .. LIMIT 1 FOR UPDATE 将允许我锁定一行,这样我就可以确保它只由一个调用者更新(更新到不再符合 SELECT 用于筛选“准备好”进行处理的作业)。
有人能告诉我我做错了什么吗?我只是有一些例子,同一个工作被分配给两个不同的进程,所以我知道它不能正常工作。:)

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
mspsb9vt

mspsb9vt1#

答案代码:

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

您所做的工作的问题是没有用于操作的原子分组。您正在使用选择。。。更新语法。文档说它阻止了“在特定事务隔离级别读取数据”。但不是所有级别(我认为)。在第一次选择和更新之间,可以从另一个线程执行另一次选择。你用的是myisam还是innodb?myisam可能不支持。
确保此操作正常工作的最简单方法是锁定表。
[编辑]我在这里描述的方法比使用 Id=(SELECT @id := Id) 方法。
另一种方法是:
具有通常设置为0的列。
执行“更新…”。。。设置colname=uniq\u id,其中colname=0限制1。这将确保只有一个进程可以更新该行,然后通过select获取它(uniq\u id不是mysql特性,只是一个变量)
如果你需要一个唯一的id,你可以使用一个自动递增的表。
你也可以用事务来实现这一点。如果在表上启动事务,请运行 UPDATE foobar SET LockVar=19 WHERE LockVar=0 LIMIT 1; 从一个线程开始,并在另一个线程上执行完全相同的操作,第二个线程将等待第一个线程提交,然后再获取其行。不过,这最终可能是一个完整的表阻塞操作。

相关问题