【问题标题】:are php files executed parallel or sequential?php 文件是并行执行还是顺序执行?
【发布时间】:2014-02-08 18:43:12
【问题描述】:

如果两个用户执行同一个php文件,是并行执行还是顺序执行?示例:

如果我有一个只有一列 id 的数据库 data,下面的代码是否有可能为两个不同的用户产生相同的结果?

1.  $db=startConnection();
2.  $query="SELECT id FROM data";
3.  $result=$db->query($query)or die($db->error);
4.  $zeile=mysqli_fetch_row($result);
5.  $number=$zeile['id'];
6.  $newnumber=$number+1;
7.  echo $number;
8.  $update = "UPDATE data Set id = '$newnumber'  WHERE id = '$number'";
9.  $db->query($query)or die($db->error);                       
10. mysqli_close($db);

如果不是并行执行,是不是意味着当100个人在加载一个加载时间为1秒的php文件时,其中一个必须等​​待99秒?


编辑:在 cmets 中说我可能会弄乱我的数据库,我想这就是它可能会搞砸的原因:

用户 A 从 1.-7 执行文件。此时用户 B 执行 1.-7 中的文件。然后 A 加载 8.-10。和 B 加载 8.-10。在这种情况下,两个用户将在屏幕上显示相同的数字。

现在让我们举个例子:

1.  $db=startConnection();
2.  $query=" INSERT INTO data  VALUES ()";
3.  $result=$db->query($query)or die($db->error);
4.  echo $db->insert_id;                        
5.  mysqli_close($db);

假设 A 从 1.-3 执行文件。此时用户 B 从 1.-5. 执行文件,之后用户 A 从 4.-5 加载文件。我想在这种情况下,屏幕上的数字也会相同,对吧?事务是否可以防止这两种情况?

【问题讨论】:

  • 两者都不是,每个都在自己独立的线程中执行;但这不会阻止您的数据库访问混乱....这就是数据库实现自动递增 id 的原因...如果您自己尝试,存在竞争条件风险,数据库自动增量消除了这种风险
  • 该代码可能并且将会为不同的用途产生相同的结果。要解决这个问题,请将您的查询包装在事务中
  • @MarkBaker 这是否意味着如果我调用插入查询,然后调用 $db->insert_id 我可以获得已在其他脚本中执行的不同插入查询的 id?
  • $db->insert_id 保证正常工作并始终返回正确的 id 而没有跨任何数量的线程重复的风险,数据库为自动增量 id 干净地处理这个......它由数据库提供正是这个目的
  • @MarkBaker 你能在我编辑的帖子中查看我的例子吗?那不是insert_id也会失败的情况吗?

标签: php


【解决方案1】:

您可以说 php 文件是并行执行的(大多数情况下是这样,但这取决于 Web 服务器)。

是的,以下代码可能会为两个不同的用户产生相同的结果。

如何避免这种可能性?

1) 如果您使用的是 MySQL,您可以使用事务“SELECT ... UPDATE FOR”来避免这种可能性。仅仅使用事务不会有帮助!

2) 确保您使用的是 InnoDB 或任何其他支持事务的数据库引擎。例如,MyISAM支持事务。如果在数据库中启用任何形式的快照来处理读取锁定记录,您也可能会遇到问题。

3) 使用“SELECT ... UPDATE FOR”的示例:

$db = startConnection();

// Start transaction
$db->query("START TRANSACTION") or die($db->error);

// Your SELECT request but with "FOR UPDATE" lock 
$query = "SELECT id FROM data FOR UPDATE";
$result = $db->query($query);


// Rollback changes if there is error
if (!$result)
{
    mysql_query("ROLLBACK");
    die($db->error);
}


$zeile = mysqli_fetch_row($result);
$number = $zeile['id'];
$newnumber = $number + 1;
echo $number;

$update = "UPDATE data Set id = '$newnumber'  WHERE id = '$number'";
$result = $db->query($query);

// Rollback changes if there is error
if (!$result)
{
    mysql_query("ROLLBACK");
    die($db->error);
}    

// Commit changes in database after requests sucessfully executed
mysql_query("COMMIT");

mysqli_close($db);

为什么只使用事务没有帮助?

Just 事务将只锁定用于写入。您可以通过在两个单独的终端窗口中运行两个 mysql 控制台客户端来测试下面的示例。我这样做了,这就是它的工作原理。

我们有并行执行的客户端#1 和客户端#2。

示例 #1。没有“SELECT ... FOR UPDATE”:

client#1: BEGIN
client#2: BEGIN
client#1: SELECT id FROM data // fetched id = 3
client#2: SELECT id FROM data // fetched id = 3
client#1: UPDATE data Set id = 4  WHERE id = 3
client#2: UPDATE data Set id = 4  WHERE id = 3
client#1: COMMIT
client#2: COMMIT

两个客户端都获取了相同的 id (3)。

示例 #2。使用“SELECT ... FOR UPDATE”:

client#1: BEGIN
client#2: BEGIN
client#1: SELECT id FROM data FOR UPDATE // fetched id = 3
client#2: SELECT id FROM data FOR UPDATE // here! client#2 will wait for end of transaction started by client#1
client#1: UPDATE data Set id = 4  WHERE id = 3
client#1: COMMIT
client#2: client#1 ended transaction and client#2 fetched id = 4
client#1: UPDATE data Set id = 5  WHERE id = 4
client#2: COMMIT

嘿,我认为这样的读锁会降低性能!

“SELECT ... FOR UPDATE”仅对使用“SELECT ... FOR UPDATE”的客户端进行读锁定。这很好,因为这意味着这种读锁不会影响没有“FOR UPDATE”的标准“SELECT”请求。

链接

MySQL documentation: "SELECT ... FOR UPDATE" and other read-locks

【讨论】:

  • 为了避免与此相关的事务问题,可能值得指出它不适用于非事务性数据库引擎,例如 MyISAM;如果在数据库中启用任何形式的快照来处理读取锁定记录,仍然会出现问题
  • 所以回滚很重要,否则表会被永远锁定?
【解决方案2】:

并行还是顺序?

您的部分问题是关于并行或顺序运行的 PHP。由于我已经阅读了有关该主题的所有内容及其相反的内容,因此我决定自己进行测试。

现场测试:

在运行 PHP 5.5 w/Apache 2 的 LAMP 堆栈上,我制作了一个带有非常昂贵循环的脚本:

function fibo($n)
{
    return ($n > 1) ? fibo($n - 1) + fibo($n - 2) : 1;
}

$start = microtime(true);
print "result: ".fibo(38);
$end = microtime(true);
print " - took ".round(($end - $start), 3).' s';

运行 1 个脚本的结果:

结果:63245986 - 耗时 19.871 秒

在两个不同的浏览器窗口中同时运行 2 个脚本的结果:

结果:63245986 - 耗时 20.753 秒

结果:63245986 - 耗时 20.847 秒

在三个不同的浏览器窗口中同时运行 3 个脚本的结果:

结果:63245986 - 耗时 26.172 秒

结果:63245986 - 耗时 28.302 秒

结果:63245986 - 耗时 28.422 秒

运行 2 个脚本实例时的 CPU 使用率:

运行 3 个脚本实例时的 CPU 使用率:

所以,它是平行的!

尽管在 PHP 脚本中,您不能轻易地使用多线程(尽管有可能),Apache 受益于您的服务器具有多个内核来分派负载。

因此,如果您的 1 秒脚本由 100 个用户同时运行,那么如果您有 100 个 CPU 内核,那么第 100 个用户几乎不会注意到任何事情。如果您有 8 个 CPU 内核(更常见),那么第 100 个用户理论上必须等待 100 / 8 = 12.5 秒才能开始他的脚本实例。实际上,正如“基准”所表明的那样,当其他线程同时在其他内核上运行时,每个线程的性能都会降低。所以它可能更多。但不会再多 100 秒。

【讨论】:

最近更新 更多