由于您没有使用事务,所以不,不会等待一个脚本中的所有查询完成,因此查询可能会重叠。
有一个称为并发编程的整个研究领域可以教授这一点。
在数据库中,它与事务、隔离级别和数据锁有关。
典型(简单)竞争条件:
$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;