我有一个执行mysql pdo查询的php脚本。 在此脚本中,对同一表进行了一些读取和写入操作。
例如,假设有 4 个查询,一个读取、写入、另一个读取、另一个写入,每次读取需要 10 秒才能执行,每次写入需要 .1 秒才能执行。
如果我在 1/100 秒内从 cli nohup php execute_queries.php &
执行此脚本两次,查询的执行顺序是什么?
来自脚本第一个实例的所有查询都需要在第二个实例的查询开始运行之前完成,还是在表被写入锁定之前开始和完成两个实例的第一次读取?
注意:假设我正在使用myisam并且写入是对记录的更新(IE,整个表在写入过程中被锁定。
由于您没有使用事务,因此不会等待一个脚本中的所有查询完成,因此查询可能会重叠。
有一个称为并发编程的整个研究领域来教授这一点。
在数据库中,它是关于事务、隔离级别和数据锁的。
典型(简单)争用条件:
$visits = $pdo->query('SELECT visits FROM articles WHERE id = 44')->fetch()[0]['visits'];
/*
* do some time-consuming thing here
*
*/
$visits++;
$pdo->exec('UPDATE articles SET visits = '.$visits.' WHERE id = 44');
如果 2 个 PHP 进程一个毫秒后一毫秒地从数据库中读取访问,并且假设访问的初始值为 6,则这两个进程都会将其增加到 7,并且都会将 7 写回数据库中,即使预期的效果是 2 次访问将值增加 2(访问的最终值应该是 8),上述竞争条件也很容易变坏。
对此的解决方案是使用原子操作(因为操作很简单,可以简化为一个原子操作)。
UPDATE articles SET visits = visits+1 WHERE id = 44;
原子操作由数据库引擎保证不受其他进程/线程的中断。通常,数据库必须对传入的更新进行排队,以便它们不会相互影响。排队显然会减慢速度,因为每个进程都必须等待所有进程才能执行。
在一个不太简单的操作中,我们需要多个语句:
SELECT @visits := visits FROM articles WHERE ID = 44;
SET @visits = @visits+1;
UPDATE articles SET visits = @visits WHERE ID = 44;
但同样,即使在数据库级别,3 个单独的原子语句也不能保证产生原子结果。它们可以与其他操作重叠。就像 PHP 示例一样。
要解决此问题,您必须执行以下操作:
START TRANSACTION
SELECT @visits := visits FROM articles WHERE ID = 44 FOR UPDATE;
SET @visits = @visits+1;
UPDATE articles SET visits = @visits WHERE ID = 44;
COMMIT;