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中不受欢迎.同样,我需要找到一种方法来允许多个脚本并行运行,并且在研究之后我已经完成了这个我想出来的.
这种方法会起作用吗?有没有更好的办法?
我认为你的方法可以工作,只要你还为你选择的当前工作的行添加某种标识符,它可能是@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/
希望这会有所帮助,并希望了解您的进展情况.
我们在生产中实现了类似的东西。
为避免重复,我们执行这样的 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。
| 归档时间: |
|
| 查看次数: |
6386 次 |
| 最近记录: |