锁定MySQL选择行,直到运行UPDATE吗?

问题描述

我有一个名为queue_items的表,该表具有3列。

'item' varchar(500) the item that has been queued
'processed_at' a datetime to mark when it has started processing,and
'completed_at' a datetime to mark when it has completed in the queue worker

这是我选择项目的方式,

SELECT `id`,`item` FROM `queue_items` WHERE `processed_at` IS NULL AND `completed_at` IS NULL ORDER BY `id` ASC LIMIT 1

一旦检查它是否存在并且可以被处理,我将执行以下操作:

UPDATE `queue_items` SET `processed_at` = @processedAt WHERE `id` = @id

问题是,在更新processed_at之前,另一个队列工作人员可以拿起我刚刚选择的项目。我该如何阻止这种情况发生?

解决方法

您可以在单个查询中执行此操作。将update与行限制子句一起使用:

UPDATE `queue_items` 
SET `processed_at` = @processedAt 
WHERE `processed_at` IS NULL AND `completed_at` IS NULL
ORDER BY id LIMIT 1

如果要保留已更新的行的id,这有点棘手。 MySQL不支持RETURNING子句,但是我们可以使用用户变量解决此问题:

SET @updated_id := 0;

UPDATE `queue_items` 
SET `processed_at` = @processedAt,id = (@x := id)
WHERE `processed_at` IS NULL AND `completed_at` IS NULL
ORDER BY id LIMIT 1;

@SELECT @updated_id;
,

围绕两个查询放置一个事务,并在FOR UPDATE查询中使用SELECT选项来锁定它检查的行。任何其他尝试读取该行的连接都将被挂起,直到提交事务为止。

确保在WHERE子句中测试的列上都有索引,因此在找到所需的行之前,不必扫描并锁定所有检查的行。

START TRANSACTION;

SELECT @id := `id`,`item` 
FROM `queue_items` 
WHERE `processed_at` IS NULL AND `completed_at` IS NULL 
ORDER BY `id` ASC 
LIMIT 1
FOR UPDATE;

UPDATE `queue_items` SET `processed_at` = @processedAt WHERE `id` = @id

COMMIT;