5

我需要能够一次从表单请求中插入 10'000 + 类似的行。目前,我已经用一个循环 10'000 次的单行准备语句完成了它,我在其中重新绑定了每个 var。

for ($i=0; $i < intval($cloneCount); $i++) 
{
    ... 9 other bindParam
    $insertG->bindParam(':v1', $v1, PDO::PARAM_STR);
    $insertG->bindParam(':v2', $v2, PDO::PARAM_INT);
    $insertG->execute();
}

这需要将近 30 秒才能实现,这当然不是一个好的做法。今天是 10,000,但明天可能是 100,000。

如果我在一个查询中插入多行,(v1,v2),(v1,v2)...我需要将每个值绑定到一个新参数,因此我相信我需要在一个查询中拥有近 100'000 个 bindedParam。如果它是 UTF-8 并且我计算每个字符大约 2 个字节(我知道它最多可以 4 个),我的查询将在 10 到 20 MB 左右,并且 mysql 服务器在另一台机器上。说到这里,我很惊讶我设计糟糕的请求只用了 30 秒就成功了。

有没有办法只发送一行并告诉 mysql 服务器复制最后一行 10'000 次?

编辑部分解决方案

遵循 Bill Karwin 和 Zsolt Szilagy 的建议。通过以下调整,我设法将 10'000 插入到远程 mysql 服务器的时间缩短到 5-6 秒:

$dataBase->beginTransaction();

$insertG = $dataBase->prepare('INSERT INTO G...)
...
10 * bindParam of all kinds

for ($i=0; $i < 10000; ++$i) 
{ 
    $hashKey = sha1(uniqid().$i); //$hashKey is a binded param
    $insertG->execute();
}
$dataBase->commit();
4

3 回答 3

12

在循环的每次迭代期间,您都不需要 bindParam() 。bindParam() 导致变量 $v1、$v2 等被引用绑定,所以您需要做的就是更改这些变量的值,然后重新执行查询。这可以减少开销。

您也可以避免每次通过循环调用 intval() 。只需确保 $cloneCount 在循环之前被强制转换为 integer once。这是一个非常小的改进,但这是一个很好的做法。

$cloneCount = (int) $cloneCount;

... 9 other bindParam
$insertG->bindParam(':v1', $v1, PDO::PARAM_STR);
$insertG->bindParam(':v2', $v2, PDO::PARAM_INT);

for ($i=0; $i < $cloneCount; $i++) 
{
  $v1 = /* something */
  $v2 = /* something */
  $insertG->execute();
}

您还应该避免自动提交。通过启动显式事务,插入数千行,然后提交事务来减少 MySQL 每条语句执行的事务开销。

但是,加速将数千个相似行批量插入到单个表的最佳方法是使用LOAD DATA LOCAL INFILE而不是 INSERT。即使您使用参数、事务、多行插入以及您能想到的任何其他技巧,它的运行速度也比逐行插入快 10-20 倍。

即使您必须使用 PHP 将数据写入 .CSV 文件到磁盘,然后在该文件上使用 LOAD DATA LOCAL INFILE,它仍然要快得多。

有关更多提示,另请参阅MySQL 手册中的 INSERT 语句的速度。

于 2013-06-02T20:34:03.520 回答
4

为批量插入构建一个包装器对象。

你想$bulkinsert->add($street,$zip);在你的循环中有类似的东西。它应该在内部构建一个带有多个插入的查询字符串:

  insert into table1 (First,Last) values 
  ("Fred","Smith"),
  ("John","Smith"),
  ("Michael","Smith"),
  ("Robert","Smith")
  ...;

我会在每 100 到 1000 次 add() 调用后执行一次。500 是查询大小和执行时间之间的良好折衷。这样您就可以节省 99.8% 当前使用的查询。

编辑:正如另一个答案中所建议的,移动循环的 count() ot。此外,使用++$i代替$i++. (长话短说,$i++ 会产生一个通常会被忽略的调用堆栈开销,但您处于一个紧密的循环中,其中微优化很重要。)

于 2013-06-02T20:33:51.067 回答
1

如果我从你的问题中理解正确有没有办法只发送一行并告诉 mysql 服务器复制最后一行 10'000 次?您需要多次复制同一行。

为此,特别是如果您经常这样做,一个计数表(与您期望的一样多的行是限制,例如 100000)并且CROSS JOIN可能有助于在 db 端和集合而不是循环来完成它。

创建一个计数表

CREATE TABLE tally(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY);

DELIMITER $$
CREATE PROCEDURE sp_populate_tally(IN n INT)
BEGIN
    DECLARE i INT DEFAULT 1;
    WHILE i <= n DO
        INSERT INTO tally VALUES (NULL);
        SET i = i + 1;
    END WHILE;
END$$
DELIMITER ;

CALL sp_populate_tally(100000);

现在复制一行 10000 次

INSERT INTO table_name (n1, n2, ...)
SELECT n1, n2, ... 
  FROM
(
  SELECT 1 n1, 'TextValue1' n2, ...
) a CROSS JOIN tally t
 WHERE t.id <= 10000;

这是SQLFidlle演示(已更新)。

于 2013-06-02T23:04:32.687 回答