MySQL事务中的查询是否保证一起执行?

时间:2013-11-12 23:09:56

标签: php mysql transactions mysqli

我有一个进程,根据某些条件从MySQL InnoDB表中选择要处理的下一个项目。当一行被选为下一个进程时,它的processing字段设置为1,而处理发生在数据库之外。我这样做是为了可以同时运行许多处理器,并且它们不会处理同一行。

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


伪代码示例

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查询(see this question)来快速完成此任务。假设这不是用于此问题的选项。

2 个答案:

答案 0 :(得分: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;

这意味着在选择行时会创建锁。这是 atomic ,这意味着没有其他会话可以潜入并锁定同一行。如果他们尝试,他们的交易将阻止SELECT。

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

Commit Transaction

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


以下是使用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

答案 1 :(得分:0)

这取决于。 SQL中存在(通常)差异隔离级别。在MySQL中,您可以使用SET TRANSACTION ISOLATION LEVEL更改要使用的那个。

虽然“SERIALIZABLE”(这是最严格的)仍然没有暗示在您的交易中没有执行任何其他操作,但它确保如果一个接一个地执行同时交易没有区别或不 - 如果它会产生影响,事务将被回滚并在以后执行。

但请注意,隔离越严格,锁定和回滚就越多。因此,确保在使用它之前确实需要它。