2

我有一个进程根据一些标准从 MySQL InnoDB 表中选择下一个要处理的项目。When a row has been selected as the next to process, it's processingfield is set to 1 while processing is happening outside the database. 我这样做是为了可以同时运行许多处理器,并且它们不会处理同一行。

如果我使用事务来执行以下查询,它们是否保证一起执行(例如,没有任何其他 MySQL 连接执行查询。)?如果不是,那么多个处理器可以id从 SELECT 查询中得到相同的结果,然后处理将是多余的。


伪代码示例

Prepare Transaction...

$id = SELECT id
FROM companies
WHERE processing = 0
ORDER BY last_crawled ASC
LIMIT 1;

UPDATE companies
SET processing = 1
WHERE id = $id;

Execute Transaction

我一直在努力使用单个 UPDATE 查询以足够快的速度完成此任务(请参阅此问题)。假设这不是这个问题的一个选项。

4

2 回答 2

4

即使您在单个事务中执行 SELECT 后跟 UPDATE,您仍然有可能出现竞争条件。SELECT 本身不会锁定任何内容,因此您可以有两个并发会话 SELECT 并获得相同的 id。然后两者都会尝试更新,但只有一个会“获胜”——另一个必须等​​待。

要解决此问题,请使用 SELECT...FOR UPDATE 子句,该子句在它返回的行上创建一个锁。

Prepare Transaction...

$id = SELECT id
FROM companies
WHERE processing = 0
ORDER BY last_crawled ASC
LIMIT 1
FOR UPDATE;

这意味着锁定是在选择行时创建的。这是原子的,这意味着没有其他会话可以潜入并获得同一行的锁定。如果他们尝试,他们的事务将在 SELECT 上阻塞。

UPDATE companies
SET processing = 1
WHERE id = $id;

Commit Transaction

我将您的“执行交易”伪代码更改为“提交交易”。事务中的语句会立即执行,这意味着它们会创建锁等等。然后,当您提交时,锁将被释放并提交任何更改。已提交意味着它们不能回滚,并且它们对其他事务可见。


这是使用 mysqli 完成此操作的快速示例:

$mysqli = new mysqli(...);
$mysqli->report_mode = MYSQLI_REPORT_STRICT; /* throw exception on error */

$mysqli->begin_transaction();
$sql = "SELECT id
    FROM companies
    WHERE processing = 0
    ORDER BY last_crawled ASC
    LIMIT 1
    FOR UPDATE";
$result = $mysqli->query($sql);
while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
    $id = $row["id"]; 
}

$sql = "UPDATE companies
    SET processing = 1
    WHERE id = ?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("i", $id);
$stmt->execute();

$mysqli->commit();

回复您的评论:

我尝试了一个实验并创建了一个表companies,用 512 行填充它,然后启动一个事务并发出SELECT...FOR UPDATE上面的语句。我是在mysql客户端做的,不需要写PHP代码。

然后,在提交我的事务之前,我检查了报告的锁:

mysql> show engine innodb status\G
=====================================
2013-12-04 16:01:28 7f6a00117700 INNODB MONITOR OUTPUT
=====================================
...
---TRANSACTION 30012, ACTIVE 2 sec
2 lock struct(s), heap size 376, 513 row lock(s)
...

尽管使用LIMIT 1了 ,但此报告显示事务似乎锁定了表中的每一行(由于某种原因,加 1)。

所以你是对的,如果你每秒有数百个请求,那么事务很可能正在排队。您应该能够通过观察SHOW PROCESSLIST和看到许多进程陷入某种状态Locked(即等待访问另一个线程已锁定的行)来验证这一点。

如果您每秒有数百个请求,您可能已经超出了 RDBMS 充当假消息队列的能力。这不是 RDBMS 擅长的。

有多种与 PHP 良好集成的可扩展消息队列框架,如 RabbitMQ、STOMP、AMQP、Gearman、Beanstalk。

查看http://www.slideshare.net/mwillbanks/message-queues-a-primer-international-php-conference-fall-2012

于 2013-11-12T23:23:40.890 回答
0

那要看。SQL 中有(通常)不同的隔离级别。在 MySQL 中,您可以使用SET TRANSACTION ISOLATION LEVEL.

虽然“SERIALIZABLE”(这是最严格的)仍然并不意味着在您的交易中没有执行其他操作,但它确实确保同时交易是否一个接一个地执行没有区别 -如果它会有所作为,事务将回滚并稍后执行。

但是请注意,隔离越严格,必须执行的锁定和回滚就越多。所以在使用它之前确保你真的需要它。

于 2013-11-12T23:15:00.987 回答