循环速度太慢

4

我有两个数组:

$questions: pid => name
$answers: pid => rid

它会将所有问题(pid)插入数据库,如果有答案(rid),则插入答案;如果没有答案,则插入0。

foreach($questions as $value) {
    $idanswer = ($answers[$value[pid]]) ? $answers[$value[pid]] : 0;
    $idquestion = $value[pid];
    $sql = "INSERT INTO solucion ( rid, pid) VALUES ( '$idanswer ', '$idquestion ')";
    $db - > query($sql);
}

由于我缺乏经验,我几乎把foreach用在所有情况下,但在这种情况下速度太慢了。有什么建议吗?


2
我非常怀疑这与“foreach”无关,而是与您的数据库查询有关。 - Svish
4
很抱歉听到这个。您应该为所有新行形成一个“INSERT”查询,而不是像我这样没经验的人几乎用foreach完成所有操作。 - Lightness Races in Orbit
1
@Svish:INSERT将很快完成;但1,500个并不会。把单个的INSERT放在一个foreach中是错误的。 - Lightness Races in Orbit
@LightnessRacesinOrbit 我可以向您保证,如果您删除 $db->query($sql);,循环将几乎是即时的。 运行查询需要时间,这意味着解决方案(就像其他人已经在此处回答的那样)是尽量减少运行的查询数量。 与“foreach的速度”无关。 - Svish
@LightnessRacesinOrbit 这个问题的标题是“foreach太慢”。但事实上,foreach非常快。另一方面,进行数百/数千个数据库查询将需要相当长的时间。这里的解决方案不是避免使用foreach,而是使用它来构建一个单一的查询,您可以在循环后运行一次,而不是为每个问题运行一个查询。 - Svish
显示剩余2条评论
5个回答

6

不要将单个INSERT查询包装在PHP循环中,而是使用PHP循环在字符串中构建一个单独的INSERT查询,然后在最后执行该单个查询。

提示:INSERT可以同时插入多行

尽管单个INSERT很快,但由于重复了连接/通信/解释/文件访问开销,每个数组元素都需要进行MySQL查询的组合往返总是会扩展得很差...这一切都是不必要的。


你还可以使用事务。只需在循环之前开始它,然后在循环结束后提交。这将提高性能。仅适用于InnoDb表引擎。 - Vadim Ashikhman
1
非常好。已在800k插入上进行了测试。而且,只有使用InnoDb才能大大提高性能。 - Vadim Ashikhman
@Vadim 但是你仍然在不必要地进行800k次往返。话虽如此,你也不会为800k行使用一个单一的字符串/查询。 - Lightness Races in Orbit
你说得对。我建议这个选项作为解决问题的另一种方式。这主要取决于主题,如果您需要获取最后插入的ID并在另一个插入中使用它并执行一些操作,则无法使用批量插入。 - Vadim Ashikhman

4

为什么不在单个查询中插入所有行,而是每行运行一个查询? 查询是您支付的昂贵成本,肯定不是foreach的问题。

为什么不尝试以下方式:

$sql = "INSERT INTO solucion (rid, pid) VALUES ";
$vls = array();
foreach($questions as $value) {
    $idanswer = ($answers[$value[pid]]) ? $answers[$value[pid]] : 0;
    $idquestion = $value[pid];
    $vls[] = " ( '$idanswer ', '$idquestion ')";

}
$sql .= implode(', ', $vls);
$db->query($sql);

1
@LightnessRacesinOrbit,我们都需要例子。 - Shoe
2
有时候,这些例子应该是对基于散文的解释的补充。你只是给了他一段代码而没有进一步的解释...这非常遗憾。这也是 Stack Overflow 被“为我制造代码”的非问题淹没的原因。 - Lightness Races in Orbit
1
OP 需要的是正确方向上的指引 - OP 只会复制粘贴你的代码,不知道它为什么这样做或者做了什么。 - Manse
1
@Jueecy 这是你的责任。这也是我们所有人的责任。否则你来这里干嘛?现在这个答案好多了。 - Lightness Races in Orbit
1
我看不出区别。 - Lightness Races in Orbit
显示剩余4条评论

2
我建议构建一个INSERT语句,插入多个值,因为每个记录的结构都是相同的。
$values = [];
foreach($questions as $value) {
  $idanswer = ($answers[$value[pid]]) ? $answers[$value[pid]] : 0;
  $idquestion = $value[pid];
  array_push($values, "('$idanswer', '$idquestion')");
 }
 $db->query("INSERT INTO solucion (rid, pid) VALUES " . implode(',', $values));

上面的代码片段将构建以下形式的INSERT语句。
INSERT INTO table (column, column) VALUES ('value', 'value'), ('value', 'value')

最好减少插入所有记录所需的数据库访问次数。
要完整了解INSERT语句,请阅读文档

2
为什么要再添加一个已经存在的答案副本... 赞同当前的答案... - Manse
我点赞了那篇文字回答,它非常清楚地描述了问题及其解决方案。感谢您指出这一点。 - Kevin Sjöberg

1
因为你在每次循环中执行1个事务,所以速度较慢。在foreach循环中构建整个SQL语句,然后在foreach完成后一次性执行它。
INSERT INTO tbl_name (a,b) VALUES(1,2),(4,5),(7,8);

0

不要运行多个插入语句,而是构建一个插入语句然后仅运行一次。例如,请参见下文。

$query = "INSERT INTO solucion ( rid, pid) VALUES";

foreach($questions as $value) {
    $idanswer = ($answers[$value[pid]]) ? $answers[$value[pid]] : 0;
    $idquestion = $value[pid];
    $query .= " ( '$idanswer ', '$idquestion '),";
}

$query = rtrim($query, ",").";";

$db - > query($sql);

3
为什么要再添加一个已经存在的答案的副本...赞同当前的答案... - Manse
我在这里没有看到任何答案就点击了“回答”,当我点击“提交”时,另一个答案已经出现了。我回答问题的速度不如其他人快,抱歉。 - llanato

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接