用PHP和MySQL实现一个简单的队列?

Nat*_*ate 18 php mysql sql queue pdo

我有一个PHP脚本,它从数据库中检索行,然后根据内容执行工作.这项工作可能很耗时(但不一定计算成本很高),因此我需要允许多个脚本并行运行.

数据库中的行看起来像这样:

+---------------------+---------------+------+-----+---------------------+----------------+
| Field               | Type          | Null | Key | Default             | Extra          |
+---------------------+---------------+------+-----+---------------------+----------------+
| id                  | bigint(11)    | NO   | PRI | NULL                | auto_increment |
.....
| date_update_started | datetime      | NO   |     | 0000-00-00 00:00:00 |                |
| date_last_updated   | datetime      | NO   |     | 0000-00-00 00:00:00 |                |
+---------------------+---------------+------+-----+---------------------+----------------+
Run Code Online (Sandbox Code Playgroud)

我的脚本当前选择具有最早日期的行date_last_updated(在完成工作后更新)并且不使用date_update_started.

如果我现在要并行运行脚本的多个实例,他们会选择相同的行(至少在某些时候),并且将完成重复的工作.

我正在考虑做的是使用事务来选择行,更新date_update_started列,然后WHERE向SQL语句添加一个条件,选择行只选择date_update_started大于某个值的行(以确保另一个脚本不起作用)在上面).例如

$sth = $dbh->prepare('
    START TRANSACTION;
    SELECT * FROM table WHERE date_update_started > 1 DAY ORDER BY date_last_updated LIMIT 1000;
    UPDATE table DAY SET date_update_started = UTC_TIMESTAMP() WHERE id IN (SELECT id FROM table WHERE date_update_started > 1 DAY ORDER BY date_last_updated LIMIT 1000;);
    COMMIT;
');
$sth->execute(); // in real code some values will be bound
$rows = $sth->fetchAll(PDO::FETCH_ASSOC);
Run Code Online (Sandbox Code Playgroud)

从我所读到的,这本质上是一个队列实现,似乎在MySQL中不受欢迎.同样,我需要找到一种方法来允许多个脚本并行运行,并且在研究之后我已经完成了这个我想出来的.

这种方法会起作用吗?有没有更好的办法?

Lup*_*pin 6

我认为你的方法可以工作,只要你还为你选择的当前工作的行添加某种标识符,它可能是@JuniusRendel建议的,我甚至会考虑使用另一个字符串键(随机或实例id)对于脚本导致错误并且没有正常完成的情况,因为在工作之后更新行后,您将不得不清理这些字段.

我认为这种方法的问题是可以选择在同一点运行2个脚本,并在签名为锁定之前选择相同的行.在这里,我可以看到它,它真的取决于你在行上做了什么样的工作,如果这两个脚本的最终结果是相同的,我认为你唯一的问题是浪费时间和服务器内存(这这不是小问题,但我暂时把它们放在一边......).如果您的工作将导致两个脚本的不同更新,您的问题将是您可能在TB的最后有错误的更新.

@Jean已经提到了你可以采用的第二种方法,涉及使用MySql锁.我不是这个主题的专家,但它似乎是一个很好的方法,并使用' 选择.... FOR UPDATE '语句可以给你你正在寻找的,因为你可以在同一个电话选择和更新 -这将比2个单独的查询更快,并且可以降低其他实例选择这些行的风险,因为它们将被锁定.

"SELECT .... FOR UPDATE"允许你运行一个SELECT语句,并锁定这些特定的行更新它们,所以你的声明看起来是这样:

START TRANSACTION;
   SELECT * FROM tb where field='value' LIMIT 1000 FOR UPDATE;
   UPDATE tb SET lock_field='1' WHERE field='value' LIMIT 1000;
COMMIT;
Run Code Online (Sandbox Code Playgroud)

锁是强大的,但要小心,它不会影响你在不同部分的应用程序.检查当前为更新锁定的所选行是否在应用程序的其他位置(可能是最终用户)请求,以及在这种情况下会发生什么.

此外,Tables必须是InnoDB,并且建议您检查where子句的字段具有Mysql索引,好像不是您可以锁定整个表或遇到" Gap Lock ".

还有可能锁定过程,特别是在运行并行脚本时,CPU和内存会很重.

这是关于这个主题的另一个阅读:http://www.percona.com/blog/2006/08/06/select-lock-in-share-mode-and-for-update/

希望这会有所帮助,并希望了解您的进展情况.


Jea*_*ean 5

我们在生产中实现了类似的东西。

为避免重复,我们执行这样的 MySQL UPDATE(我修改了查询以类似于您的表):

UPDATE queue SET id = LAST_INSERT_ID(id), date_update_started = ... 
WHERE date_update_started IS NULL AND ...
LIMIT 1;
Run Code Online (Sandbox Code Playgroud)

我们在单个事务中执行此更新,并利用该LAST_INSERT_ID功能。当像这样使用时,带有参数,它会在事务会话中写入参数,在这种情况下,它是LIMIT 1已更新的单个 ( ) 队列的 ID (如果有)。

在那之后,我们这样做:

SELECT LAST_INSERT_ID();
Run Code Online (Sandbox Code Playgroud)

当不带参数使用时,它会检索先前存储的值,获取必须执行的队列项的 ID。