【问题标题】:php PDO insert batch multiple rows with placeholdersphp PDO使用占位符插入批处理多行
【发布时间】:2013-02-10 18:33:34
【问题描述】:

我希望使用 PHP PDO 进行多次插入。

我找到的最接近的答案是这个

how-to-insert-an-array-into-a-single-mysql-prepared-statement

但是给出的示例使用 ??而不是真正的占位符。

我已经查看了 PHP 文档站点上的占位符示例

php.net pdo.prepared-statements

$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");
$stmt->bindParam(':name', $name);
$stmt->bindParam(':value', $value);

现在假设我想通过数组实现上述目标

$valuesToInsert = array(
  0 => array('name' => 'Robert', 'value' => 'some value'),
  1 => array('name' -> 'Louise', 'value' => 'another value')
);

我将如何处理 PDO 和每个事务的多个插入?

我想它会从一个循环开始?

$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");

foreach($valuesToInsert as $insertRow){

    // now loop through each inner array to match binded values
    foreach($insertRow as $column => value){
        $stmt->bindParam(":{$column}", value);
    }
}
$stmt->execute();

但是上述方法不起作用,但希望能证明我试图实现的目标

【问题讨论】:

  • 你需要在循环内执行。否则你只是覆盖了绑定参数并最终只绑定了 LAST 值。
  • 但是,如果我执行,那么它会一次执行一行数据库事务吗?我正在尝试批量进行
  • 是的,但是如果您尝试模拟 mysql 扩展 insert ... values (...), (...), (...) 插入语法,这不是您的做法。您必须预先构建一个查询语句,该语句为您插入的每组值都有一个占位符,准备它,绑定参数,然后执行。你最终所做的工作与多次运行一个准备好的插入一样多。
  • 我一次插入大约 40 或 50k 行。我需要分批做。它是占用时间的数据库事务。
  • JFYI:??真实 占位符,:name 是假占位符。

标签: php insert pdo bulkinsert


【解决方案1】:

首先,? 符号真正的占位符(大多数驱动程序允许同时使用语法、位置和命名占位符)。其次,预准备语句只不过是一种将原始输入注入 SQL 语句的工具——SQL 语句本身的语法不受影响。您已经拥有所需的所有元素:

  • 如何使用单个查询插入多行
  • 如何动态生成 SQL
  • 如何使用带有命名占位符的预准备语句。

将它们全部组合起来相当简单:

$sql = 'INSERT INTO table (memberID, programID) VALUES ';
$insertQuery = [];
$insertData = [];
$n = 0;
foreach ($data as $row) {
    $insertQuery[] = '(:memberID' . $n . ', :programID' . $n . ')';
    $insertData['memberID' . $n] = $memberid;
    $insertData['programID' . $n] = $row;
    $n++;
}

if (!empty($insertQuery)) {
    $sql .= implode(', ', $insertQuery);
    $stmt = $db->prepare($sql);
    $stmt->execute($insertData);
}

【讨论】:

  • 请注意,在大型数据集上,您将使用 SQL VALUES 子句 (大约 1 000 行) 达到限制,并且您可能必须限制批处理尺寸。我注意到增加批量大小并不总是有效的,所以用不同的批量大小进行测试。确保您命名的占位符最后没有使用除 $n 之外的数字以避免冲突。
  • 另见array_chunk
  • 实验结果:使用 array_chunk 和 100 行在使用事务的 InnoDB 上实现了 2 倍的速度提升。更大的批次并没有更快。
【解决方案2】:

您的代码实际上没问题,但$stmt->bindParam(":$column", value); 有问题,应该是$stmt->bindValue(":{$column}", $value);,它会完美运行。这将在未来对其他人有所帮助。

完整代码:

foreach($params as $row)
{ 
    // now loop through each inner array to match bound values
    foreach($row as $column => $value)
    { 
        $stmt->bindValue(":{$column}", $value); //EDIT
    }
    // Execute statement to add to transaction
    $stmt->execute();
} 

【讨论】:

  • 这部分语法没有一个问题。您的建议是多余的,具有误导性。
【解决方案3】:

对 N.B 提供的解决方案稍作修改
$stmt->execute() 应该在内部循环之外,因为在调用 $stmt->execute() 之前你可能有一个或多个列需要绑定,否则你会得到异常“无效的参数号:绑定变量的数量确实令牌数不匹配”。
第二个“价值”变量缺少美元符号。

function batchinsert($sql,$params){
    try { 
                db->beginTransaction(); 

                $stmt = db->prepare($sql);

                foreach($params as $row)
                {    
                    // now loop through each inner array to match bound values
                    foreach($row as $column => $value)
                    {                           
                        $stmt->bindParam(":$column", $value);                           
                    }
                    $stmt->execute();
                }                                       
                db->commit();                   

        } catch(PDOExecption $e) {
            $db->rollback();                
        }
}

测试:

$sql = "INSERT INTO `test`(`name`, `value`) VALUES (:name, :value)" ;

$data = array();    

array_push($data, array('name'=>'Name1','value'=>'Value1')); 

array_push($data, array('name'=>'Name2','value'=>'Value2')); 

array_push($data, array('name'=>'Name3','value'=>'Value3')); 

array_push($data, array('name'=>'Name4','value'=>'Value4')); 

array_push($data, array('name'=>'Name5','value'=>'Value5')); 

batchinsert($sql,$data);

【讨论】:

    【解决方案4】:

    我假设您使用的是 InnoDB,因此此答案仅对该引擎有效(或任何其他支持事务的引擎,这意味着不包括 MyISAM)。

    默认情况下 InnoDB 在自动提交模式下运行。这意味着每个查询都被视为自己包含的事务。

    将其翻译为我们凡人可以理解的内容,这意味着您发出的每个 INSERT 查询都会通过确认它写下查询信息来强制硬盘提交它。 考虑到机械硬盘的速度非常慢,因为它们每秒的输入输出操作很低(如果我没记错的话,平均是 300 次 IO),这意味着您的 50 000 次查询将会 - 非常慢。

    那你是做什么的?您在单个事务中提交所有 50k 查询。它可能不是用于各种目的的最佳解决方案,但它会很快。

    你这样做:

    $dbh->beginTransaction();
    
    $stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");
    
    foreach($valuesToInsert as $insertRow)
    {    
        // now loop through each inner array to match bound values
        foreach($insertRow as $column => value)
        {
            $stmt->bindParam(":$column", value);
            $stmt->execute();
        }
    }
    
    
    $dbh->commit();
    

    【讨论】:

    • 我有大约 50k 行要插入,并且打算分批插入 25 行。我会试试这个例子,因为它看起来可能是我要找的。​​span>
    • 这个答案是正确的,但它没有谈到 MySQL 的多插入语法,根据我的经验,它可以真正加快大批量的速度。
    • 为了获得最佳性能,LOAD DATA INFILE 仍然是最快的选项,并且构造要与LOAD DATA INFILE 一起使用的文件应该相当简单。我忘了将其添加到答案中,正如@ÁlvaroG.Vicario 提到的那样-INSERT INTO .... VALUES.. 比在循环中执行准备好的语句要快。
    • “值”变量缺少美元符号 :)
    【解决方案5】:

    将执行移到循环内。

    $stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");
    foreach($valuesToInsert as $insertRow)
    {
        $stmt->execute($insertRow);    
    }
    

    如果您在使用这种推荐方式时遇到任何问题,您必须提出问题,描述这些特定问题。

    【讨论】:

    • 我不认为上面是批量做的,看起来像是一次做一个?
    • 这不是批量插入,您很清楚每行多次重复使用相同的 SQL 语句,但它仍然一次执行单个插入。
    猜你喜欢
    • 2020-05-25
    • 2012-04-20
    • 2015-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-22
    • 2011-11-23
    相关资源
    最近更新 更多