如何在没有并发问题的情况下选择行并更新它们?

时间:2014-12-25 16:35:12

标签: php mysql select transactions sql-update

我正在使用PHP和MySQL 5.6.17(InnoDB)实现一个队列,我想选择前N个匹配的行,然后将它们标记为正在处理。

我需要将行标记为正在处理,因为查询是由多个并行运行的脚本执行的(因此我需要阻止脚本选择相同的行)。

我写了以下查询:

START TRANSACTION;

SELECT id, col2, col3
FROM table
WHERE col4 = 1 AND date_update_started < UTC_TIMESTAMP() - INTERVAL 12 HOUR
ORDER BY col5 DESC, col6 ASC
LIMIT 100 FOR UPDATE;

#update the above selected rows to mark them as being processed
UPDATE table SET date_update_started = UTC_TIMESTAMP() WHERE id IN (
    SELECT id, col2, col3 #same query as above
    FROM table
    WHERE col4 = 1 AND date_update_started < UTC_TIMESTAMP() - INTERVAL 12 HOUR
    ORDER BY col5 DESC, col6 ASC
    LIMIT 100
);

COMMIT;

但是,在测试查询的更新部分时,我收到以下错误:

  

[Err] 1235 - 这个版本的MySQL还没有支持&#39; LIMIT&amp; IN / ALL / ANY / SOME子查询&#39;

如何修改此查询,以便它选择前N个匹配行并更新这些行上的date_update_started列,以便并行执行此查询的脚本不会选择它们?

1 个答案:

答案 0 :(得分:1)

确保脚本的每个实例都具有唯一ID。您可以在运行时将其作为命令行参数传递。

在队列表中添加一列:

  • scriptId INT DEFAULT NULL - 用它锁定一些行;保留锁定它们的脚本的ID。

此代码会锁定某些行:

UPDATE `table`
SET lockId = 123    # Replace '123' (in PHP) with the ID of the script that runs the query
WHERE lockId IS NULL
   AND ...  # put your own conditions here to select the entries you want to process
LIMIT 100   # change '100' with the number of entries you want to lock in a batch

然后运行:

SELECT *
FROM `table`
WHERE lockId = 123    # The same value as above

获取锁定的行。

处理完每一行后,您可以将其从表格中删除,也可以将状态字段设置为“已处理”。并使用它在上面的锁定查询中过滤掉它。

备注:如果您完全确定处理脚本在处理期间永不崩溃,则此方法很有效。如果它崩溃,它会使行被锁定。如果在下次运行时它使用相同的script ID,它将尝试处理锁定的行。这可以通过在脚本退出时解锁行来解决。