我有一个进程,根据某些条件从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)来快速完成此任务。假设这不是用于此问题的选项。
答案 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”(这是最严格的)仍然没有暗示在您的交易中没有执行任何其他操作,但它确保如果一个接一个地执行同时交易没有区别或不 - 如果它会产生影响,事务将被回滚并在以后执行。
但请注意,隔离越严格,锁定和回滚就越多。因此,确保在使用它之前确实需要它。