在单个查询中插入多行

我目前在 MySQL 上使用这种类型的 SQL 在一个查询中插入多行值:

INSERT INTO `tbl` (`key1`,`key2`) VALUES ('r1v1','r1v2'),('r2v1','r2v2'),...

关于 PDO 的读数,use 准备好的语句应该比静态查询提供更好的安全性。

因此,我想知道是否可以使用准备好的语句生成“通过使用一个查询插入多行值”。

如果是,我可以知道如何实现它吗?

197785 次浏览

这根本不是使用事先准备好的语句的方式。

每个查询插入一行是完全可以的,因为可以使用不同的参数多次执行一条准备好的语句。事实上,这是最大的优势之一,因为它允许您以高效、安全和舒适的方式插入大量行。

因此,也许可以实现您提出的方案,至少对于固定数量的行,但几乎可以肯定的是,这不是您真正想要的。

有两种可能的方法:

$stmt = $pdo->prepare('INSERT INTO foo VALUES(:v1_1, :v1_2, :v1_3),
(:v2_1, :v2_2, :v2_3),
(:v2_1, :v2_2, :v2_3)');
$stmt->bindValue(':v1_1', $data[0][0]);
$stmt->bindValue(':v1_2', $data[0][1]);
$stmt->bindValue(':v1_3', $data[0][2]);
// etc...
$stmt->execute();

或者:

$stmt = $pdo->prepare('INSERT INTO foo VALUES(:a, :b, :c)');
foreach($data as $item)
{
$stmt->bindValue(':a', $item[0]);
$stmt->bindValue(':b', $item[1]);
$stmt->bindValue(':c', $item[2]);
$stmt->execute();
}

如果所有行的数据都在一个数组中,我将使用第二种解决方案。

使用 PDO 准备的语句插入多个值

在一个执行语句中插入多个值。为什么? 因为根据 这一页,它比常规的插入要快。

$datafields = array('fielda', 'fieldb', ... );


$data[] = array('fielda' => 'value', 'fieldb' => 'value' ....);
$data[] = array('fielda' => 'value', 'fieldb' => 'value' ....);

更多的数据值,或者您可能有一个填充数据的循环。

对于准备好的插入,您需要知道要插入的字段,以及创建?占位符来绑定参数。

insert into table (fielda, fieldb, ... ) values (?,?...), (?,?...)....

这基本上就是我们希望 insert 语句看起来的样子。

现在,密码是:

function placeholders($text, $count=0, $separator=","){
$result = array();
if($count > 0){
for($x=0; $x<$count; $x++){
$result[] = $text;
}
}


return implode($separator, $result);
}


$pdo->beginTransaction(); // also helps speed up your inserts.
$insert_values = array();
foreach($data as $d){
$question_marks[] = '('  . placeholders('?', sizeof($d)) . ')';
$insert_values = array_merge($insert_values, array_values($d));
}


$sql = "INSERT INTO table (" . implode(",", $datafields ) . ") VALUES " .
implode(',', $question_marks);


$stmt = $pdo->prepare ($sql);
$stmt->execute($insert_values);
$pdo->commit();

虽然在我的测试中,当使用多个插入和具有单个值的常规准备插入时,只有1秒的差异。

答案和巴拉格塔斯先生一样,稍微清楚一点。

最新版本的 MySQL 和 PHP PDO 支持多行 INSERT语句。

SQL 概述

假设有一个包含3列的表,您希望 INSERT是这样的,那么 SQL 看起来应该是这样的。

INSERT INTO tbl_name
(colA, colB, colC)
VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?) [,...]

即使使用多行 INSERT,ON DUPLICATE KEY UPDATE也能正常工作; 附加以下内容:

ON DUPLICATE KEY UPDATE colA = VALUES(colA), colB = VALUES(colB), colC = VALUES(colC)

PHP 概述

您的 PHP 代码将遵循通常的 $pdo->prepare($qry)$stmt->execute($params) PDO 调用。

$params将是一个由 所有组成的一维数组,这些值将传递给 INSERT

在上面的示例中,它应该包含9个元素; PDO 将使用每组3作为一行值。(插入3行3列,每行 = 9个元素数组。)

实施

下面的代码是为了清晰而非效率而编写的。如果愿意,可以使用 PHP array_*()函数来更好地映射或遍历数据。显然,您是否可以使用事务取决于您的 MySQL 表类型。

假设:

  • $tblName-要 INSERT 到的表的字符串名称
  • 表的列名的 $colNames-1维数组 这些列名必须是有效的 MySQL 列标识符; 如果不是,则用反勾(“)转义
  • $dataVals-mutli 维数组,其中每个元素是一个1-d 数组,包含一行 INSERT 值

样本代码

// setup data values for PDO
// memory warning: this is creating a copy all of $dataVals
$dataToInsert = array();


foreach ($dataVals as $row => $data) {
foreach($data as $val) {
$dataToInsert[] = $val;
}
}


// (optional) setup the ON DUPLICATE column names
$updateCols = array();


foreach ($colNames as $curCol) {
$updateCols[] = $curCol . " = VALUES($curCol)";
}


$onDup = implode(', ', $updateCols);


// setup the placeholders - a fancy way to make the long "(?, ?, ?)..." string
$rowPlaces = '(' . implode(', ', array_fill(0, count($colNames), '?')) . ')';
$allPlaces = implode(', ', array_fill(0, count($dataVals), $rowPlaces));


$sql = "INSERT INTO $tblName (" . implode(', ', $colNames) .
") VALUES " . $allPlaces . " ON DUPLICATE KEY UPDATE $onDup";


// and then the PHP PDO boilerplate
$stmt = $pdo->prepare ($sql);


$stmt->execute($dataToInsert);


$pdo->commit();

一个简短的答案是: 然后将按列排序的数据数组扁平化

//$array = array( '1','2','3','4','5', '1','2','3','4','5');
$arCount = count($array);
$rCount = ($arCount  ? $arCount - 1 : 0);
$criteria = sprintf("(?,?,?,?,?)%s", str_repeat(",(?,?,?,?,?)", $rCount));
$sql = "INSERT INTO table(c1,c2,c3,c4,c5) VALUES$criteria";

在插入1,000条左右的记录时,如果只需要对值进行计数,那么不需要循环遍历每条记录来插入这些记录。

值得一提的是,我看到很多用户建议迭代 INSERT 语句,而不是像选择的答案那样构建为单个字符串查询。我决定运行一个只有两个字段和一个非常基本的 insert 语句的简单测试:

<?php
require('conn.php');


$fname = 'J';
$lname = 'M';


$time_start = microtime(true);
$stmt = $db->prepare('INSERT INTO table (FirstName, LastName) VALUES (:fname, :lname)');


for($i = 1; $i <= 10; $i++ )  {
$stmt->bindParam(':fname', $fname);
$stmt->bindParam(':lname', $lname);
$stmt->execute();


$fname .= 'O';
$lname .= 'A';
}




$time_end = microtime(true);
$time = $time_end - $time_start;


echo "Completed in ". $time ." seconds <hr>";


$fname2 = 'J';
$lname2 = 'M';


$time_start2 = microtime(true);
$qry = 'INSERT INTO table (FirstName, LastName) VALUES ';
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?)";


$stmt2 = $db->prepare($qry);
$values = array();


for($j = 1; $j<=10; $j++) {
$values2 = array($fname2, $lname2);
$values = array_merge($values,$values2);


$fname2 .= 'O';
$lname2 .= 'A';
}


$stmt2->execute($values);


$time_end2 = microtime(true);
$time2 = $time_end2 - $time_start2;


echo "Completed in ". $time2 ." seconds <hr>";
?>

虽然整个查询本身花费的时间不超过毫秒,但后一个(单个字符串)查询的速度始终是前者的8倍或更多。如果构建这个函数是为了说明在更多的列上导入了数千行,那么差异可能是巨大的。

Herbert Balagtas 的“接受的答案”在 $data 数组很小的时候工作得很好。对于更大的 $data 数组,array _ merge 函数会变得非常慢。用于创建 $data 数组的测试文件有28种协议,大约有80,000行。最终的脚本需要 41来完成。

使用 Array _ push ()创建 $insert _ value 而不是 array _ merge ()导致 加速100倍的执行时间为 0.41口径

有问题的 array _ merge () :

$insert_values = array();


foreach($data as $d){
$question_marks[] = '('  . placeholders('?', sizeof($d)) . ')';
$insert_values = array_merge($insert_values, array_values($d));
}

为了消除 array _ merge ()的需要,可以改为构建以下两个数组:

//Note that these fields are empty, but the field count should match the fields in $datafields.
$data[] = array('','','','',... n );


//getting rid of array_merge()
array_push($insert_values, $value1, $value2, $value3 ... n );

这些数组可用于以下方面:

function placeholders($text, $count=0, $separator=","){
$result = array();
if($count > 0){
for($x=0; $x<$count; $x++){
$result[] = $text;
}
}


return implode($separator, $result);
}


$pdo->beginTransaction();


foreach($data as $d){
$question_marks[] = '('  . placeholders('?', sizeof($d)) . ')';
}


$sql = "INSERT INTO table (" . implode(",", array_keys($datafield) ) . ") VALUES " . implode(',', $question_marks);


$stmt = $pdo->prepare($sql);
$stmt->execute($insert_values);
$pdo->commit();

下面是我编写的一个用 purge 选项执行多次插入的类:

<?php


/**
* $pdo->beginTransaction();
* $pmi = new PDOMultiLineInserter($pdo, "foo", array("a","b","c","e"), 10);
* $pmi->insertRow($data);
* ....
* $pmi->insertRow($data);
* $pmi->purgeRemainingInserts();
* $pdo->commit();
*
*/
class PDOMultiLineInserter {
private $_purgeAtCount;
private $_bigInsertQuery, $_singleInsertQuery;
private $_currentlyInsertingRows  = array();
private $_currentlyInsertingCount = 0;
private $_numberOfFields;
private $_error;
private $_insertCount = 0;


function __construct(\PDO $pdo, $tableName, $fieldsAsArray, $bigInsertCount = 100) {
$this->_numberOfFields = count($fieldsAsArray);
$insertIntoPortion = "INSERT INTO `$tableName` (`".implode("`,`", $fieldsAsArray)."`) VALUES";
$questionMarks  = " (?".str_repeat(",?", $this->_numberOfFields - 1).")";


$this->_purgeAtCount = $bigInsertCount;
$this->_bigInsertQuery    = $pdo->prepare($insertIntoPortion.$questionMarks.str_repeat(", ".$questionMarks, $bigInsertCount - 1));
$this->_singleInsertQuery = $pdo->prepare($insertIntoPortion.$questionMarks);
}


function insertRow($rowData) {
// @todo Compare speed
// $this->_currentlyInsertingRows = array_merge($this->_currentlyInsertingRows, $rowData);
foreach($rowData as $v) array_push($this->_currentlyInsertingRows, $v);
//
if (++$this->_currentlyInsertingCount == $this->_purgeAtCount) {
if ($this->_bigInsertQuery->execute($this->_currentlyInsertingRows) === FALSE) {
$this->_error = "Failed to perform a multi-insert (after {$this->_insertCount} inserts), the following errors occurred:".implode('<br/>', $this->_bigInsertQuery->errorInfo());
return false;
}
$this->_insertCount++;


$this->_currentlyInsertingCount = 0;
$this->_currentlyInsertingRows = array();
}
return true;
}


function purgeRemainingInserts() {
while ($this->_currentlyInsertingCount > 0) {
$singleInsertData = array();
// @todo Compare speed - http://www.evardsson.com/blog/2010/02/05/comparing-php-array_shift-to-array_pop/
// for ($i = 0; $i < $this->_numberOfFields; $i++) $singleInsertData[] = array_pop($this->_currentlyInsertingRows); array_reverse($singleInsertData);
for ($i = 0; $i < $this->_numberOfFields; $i++) array_unshift($singleInsertData, array_pop($this->_currentlyInsertingRows));


if ($this->_singleInsertQuery->execute($singleInsertData) === FALSE) {
$this->_error = "Failed to perform a small-insert (whilst purging the remaining rows; the following errors occurred:".implode('<br/>', $this->_singleInsertQuery->errorInfo());
return false;
}
$this->_currentlyInsertingCount--;
}
}


public function getError() {
return $this->_error;
}
}

使用此函数可以在单个查询中插入多行:

function insertMultiple($query,$rows) {
if (count($rows)>0) {
$args = array_fill(0, count($rows[0]), '?');


$params = array();
foreach($rows as $row)
{
$values[] = "(".implode(',', $args).")";
foreach($row as $value)
{
$params[] = $value;
}
}


$query = $query." VALUES ".implode(',', $values);
$stmt = $PDO->prepare($query);
$stmt->execute($params);
}
}

$row 是值数组的 数组。 在您的情况下,您可以使用

insertMultiple("INSERT INTO tbl (`key1`,`key2`)",array(array('r1v1','r1v2'),array('r2v1','r2v2')));

这样做的好处是,您可以使用 事先准备好的陈述,同时通过一个查询插入多个行!

这招对我很管用

$sql = 'INSERT INTO table(pk_pk1,pk_pk2,date,pk_3) VALUES ';
$qPart = array_fill(0, count($array), "(?, ?,UTC_TIMESTAMP(),?)");
$sql .= implode(",", $qPart);
$stmt =    DB::prepare('base', $sql);
$i = 1;
foreach ($array as $value) {
$stmt->bindValue($i++, $value);
$stmt->bindValue($i++, $pk_pk1);
$stmt->bindValue($i++, $pk_pk2);
$stmt->bindValue($i++, $pk_pk3);
}
$stmt->execute();

这是我的解决方案: < a href = “ https://github.com/sasha-ch/Aura.Sql”rel = “ nofollow”> https://github.com/sasha-ch/aura 基于 auraphp/Aura. Sql 库。

用法例子:

$q = "insert into t2(id,name) values (?,?), ... on duplicate key update name=name";
$bind_values = [ [[1,'str1'],[2,'str2']] ];
$pdo->perform($q, $bind_values);

欢迎来到布格波特。

我在现实世界中的例子是将所有德国邮政编码插入到一个空表中(以便稍后添加城镇名称) :

// obtain column template
$stmt = $db->prepare('SHOW COLUMNS FROM towns');
$stmt->execute();
$columns = array_fill_keys(array_values($stmt->fetchAll(PDO::FETCH_COLUMN)), null);
// multiple INSERT
$postcode = '01000';// smallest german postcode
while ($postcode <= 99999) {// highest german postcode
$values = array();
while ($postcode <= 99999) {
// reset row
$row = $columns;
// now fill our row with data
$row['postcode'] = sprintf('%05d', $postcode);
// build INSERT array
foreach ($row as $value) {
$values[] = $value;
}
$postcode++;
// avoid memory kill
if (!($postcode % 10000)) {
break;
}
}
// build query
$count_columns = count($columns);
$placeholder = ',(' . substr(str_repeat(',?', $count_columns), 1) . ')';//,(?,?,?)
$placeholder_group = substr(str_repeat($placeholder, count($values) / $count_columns), 1);//(?,?,?),(?,?,?)...
$into_columns = implode(',', array_keys($columns));//col1,col2,col3
// this part is optional:
$on_duplicate = array();
foreach ($columns as $column => $row) {
$on_duplicate[] = $column;
$on_duplicate[] = $column;
}
$on_duplicate = ' ON DUPLICATE KEY UPDATE' . vsprintf(substr(str_repeat(', %s = VALUES(%s)', $count_columns), 1), $on_duplicate);
// execute query
$stmt = $db->prepare('INSERT INTO towns (' . $into_columns . ') VALUES' . $placeholder_group . $on_duplicate);//INSERT INTO towns (col1,col2,col3) VALUES(?,?,?),(?,?,?)... {ON DUPLICATE...}
$stmt->execute($values);
}

正如你所看到的,它是完全灵活的。您不需要检查列的数量或检查列的位置。您只需要设置插入数据:

    $row['postcode'] = sprintf('%05d', $postcode);

我为一些查询字符串构造函数感到自豪,因为它们不需要大量的数组函数(如 array _ merge)就可以工作。尤其是 vsprintf ()是一个很好的发现。

最后,我需要添加2x while ()以避免超过内存限制。这取决于您的内存限制,但总的来说,这是避免问题的一个很好的通用解决方案(有10个查询仍然比10.000好得多)。

我是这么做的:

首先定义您将使用的列名,或者将其保留为空,pdo 将假定您希望使用表中的所有列——在这种情况下,您需要以表中出现的确切顺序通知行值。

$cols = 'name', 'middleName', 'eMail';
$table = 'people';

现在,假设您已经准备好了一个二维数组。迭代它,并使用您的行值构造一个字符串,如下所示:

foreach ( $people as $person ) {
if(! $rowVals ) {
$rows = '(' . "'$name'" . ',' . "'$middleName'" . ',' .           "'$eMail'" . ')';
} else { $rowVals  = '(' . "'$name'" . ',' . "'$middleName'" . ',' . "'$eMail'" . ')';
}

现在,您刚才所做的就是检查 $rows 是否已经定义,如果没有,创建它并存储行值和必要的 SQL 语法,这样它就是一个有效的语句。请注意,字符串应该放在双引号和单引号中,这样它们将被迅速识别为双引号。

剩下要做的就是准备语句并执行:

$stmt = $db->prepare ( "INSERT INTO $table $cols VALUES $rowVals" );
$stmt->execute ();

到目前为止测试了多达2000行,执行时间非常糟糕。将运行一些更多的测试,并将回到这里,如果我有更多的贡献。

问候。

这里提供的用于创建准备好的查询的大多数解决方案都比它们需要的更复杂。使用 PHP 的内置函数,您可以轻松地创建 SQL 语句,而不会产生很大的开销。

给定 $records(一个记录数组,其中每条记录本身是一个索引数组(以 field => value的形式)) ,下面的函数将在一个 PDO 连接 $connection上,仅使用一个准备好的语句,将记录插入给定的表 $table。注意,这是一个 PHP 5.6 + 解决方案,因为在对 array_push的调用中使用了参数解包:

private function import(PDO $connection, $table, array $records)
{
$fields = array_keys($records[0]);
$placeHolders = substr(str_repeat(',?', count($fields)), 1);
$values = [];
foreach ($records as $record) {
array_push($values, ...array_values($record));
}


$query = 'INSERT INTO ' . $table . ' (';
$query .= implode(',', $fields);
$query .= ') VALUES (';
$query .= implode('),(', array_fill(0, count($records), $placeHolders));
$query .= ')';


$statement = $connection->prepare($query);
$statement->execute($values);
}

由于还没有建议使用 LOAD DATA INFILE,因此我相当肯定 LOAD DATA INFILE 仍然是加载数据的最快方法,因为它禁用索引,插入所有数据,然后重新启用索引——所有这些都在一个请求中。

将数据保存为 csv 应该相当简单,记住 fputcsv。MyISAM 是最快的,但是在 InnoDB 中仍然有很大的性能。还有其他的缺点,所以如果您要插入大量数据,而且不需要低于100行,那么我会采用这种方法。

虽然一个老问题,所有的贡献帮助了我很多,所以这里是我的解决方案,这在我自己的 DbContext类。$rows参数只是表示行或模型的关联数组的数组: field name => insert value

如果您使用使用模型的模式,那么当以数组形式传递模型数据时,比如说从模型类中的 ToRowArray方法传递模型数据时,这种模式非常适合。

注意 : 它应该不言而喻,但绝不允许传递参数 对此方法公开给用户或依赖任何用户输入(插入值除外) ,这些值已经过验证和消毒。$tableName参数和列名应该由调用逻辑定义; 例如,User模型可以映射到用户表,用户表将其列表映射到模型的成员字段。

public function InsertRange($tableName, $rows)
{
// Get column list
$columnList = array_keys($rows[0]);
$numColumns = count($columnList);
$columnListString = implode(",", $columnList);


// Generate pdo param placeholders
$placeHolders = array();


foreach($rows as $row)
{
$temp = array();


for($i = 0; $i < count($row); $i++)
$temp[] = "?";


$placeHolders[] = "(" . implode(",", $temp) . ")";
}


$placeHolders = implode(",", $placeHolders);


// Construct the query
$sql = "insert into $tableName ($columnListString) values $placeHolders";
$stmt = $this->pdo->prepare($sql);


$j = 1;
foreach($rows as $row)
{
for($i = 0; $i < $numColumns; $i++)
{
$stmt->bindParam($j, $row[$columnList[$i]]);
$j++;
}
}


$stmt->execute();
}

这是我的简单方法。

    $values = array();
foreach($workouts_id as $value){
$_value = "(".$value.",".$plan_id.")";
array_push($values,$_value);
}
$values_ = implode(",",$values);


$sql = "INSERT INTO plan_days(id,name) VALUES" . $values_."";
$stmt = $this->conn->prepare($sql);
$stmt->execute();

我也遇到过同样的问题,这就是我如何为自己完成的,我为自己做了一个函数(如果这对你有帮助的话,你可以使用它)。

例如:

插入国家(国家,城市)价值观(德国,柏林) ,(法国,巴黎) ;

$arr1 = Array("Germany", "Berlin");
$arr2 = Array("France", "France");


insertMultipleData("countries", Array($arr1, $arr2));




// Inserting multiple data to the Database.
public function insertMultipleData($table, $multi_params){
try{
$db = $this->connect();


$beforeParams = "";
$paramsStr = "";
$valuesStr = "";


for ($i=0; $i < count($multi_params); $i++) {


foreach ($multi_params[$i] as $j => $value) {


if ($i == 0) {
$beforeParams .=  " " . $j . ",";
}


$paramsStr .= " :"  . $j . "_" . $i .",";
}


$paramsStr = substr_replace($paramsStr, "", -1);
$valuesStr .=  "(" . $paramsStr . "),";
$paramsStr = "";
}




$beforeParams = substr_replace($beforeParams, "", -1);
$valuesStr = substr_replace($valuesStr, "", -1);




$sql = "INSERT INTO " . $table . " (" . $beforeParams . ") VALUES " . $valuesStr . ";";


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




for ($i=0; $i < count($multi_params); $i++) {
foreach ($multi_params[$i] as $j => &$value) {
$stmt->bindParam(":" . $j . "_" . $i, $value);
}
}


$this->close($db);
$stmt->execute();


return true;


}catch(PDOException $e){
return false;
}


return false;
}


// Making connection to the Database
public function connect(){
$host = Constants::DB_HOST;
$dbname = Constants::DB_NAME;
$user = Constants::DB_USER;
$pass = Constants::DB_PASS;


$mysql_connect_str = 'mysql:host='. $host . ';dbname=' .$dbname;


$dbConnection = new PDO($mysql_connect_str, $user, $pass);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);


return $dbConnection;
}


// Closing the connection
public function close($db){
$db = null;
}

如果 插入 MultipleData ($table,$multi _ params) 返回真,则说明数据已插入到数据库中。

根据我的实验,我发现在单个事务中带有多个值行的 mysql 插入语句是最快的。

但是,如果数据太多,mysql 的 max_allowed_packet设置可能会限制使用多个值行的单个事务插入。因此,当数据大于 mysql 的 max_allowed_packet大小时,以下函数将失败:

  1. singleTransactionInsertWithRollback
  2. singleTransactionInsertWithPlaceholders
  3. singleTransactionInsert

在插入海量数据场景中,最成功的方法是 transactionSpeed方法,但它比上述方法更耗时。因此,要处理这个问题,您可以将数据分割成更小的块,多次调用单个事务插入,或者使用 transactionSpeed方法放弃执行速度。

这是我的研究

<?php


class SpeedTestClass
{
private $data;


private $pdo;


public function __construct()
{
$this->data = [];
$this->pdo = new \PDO('mysql:dbname=test_data', 'admin', 'admin');
if (!$this->pdo) {
die('Failed to connect to database');
}
}


public function createData()
{
$prefix = 'test';
$postfix = 'unicourt.com';
$salutations = ['Mr.', 'Ms.', 'Dr.', 'Mrs.'];


$csv[] = ['Salutation', 'First Name', 'Last Name', 'Email Address'];
for ($i = 0; $i < 100000; ++$i) {
$csv[] = [
$salutations[$i % \count($salutations)],
$prefix.$i,
$prefix.$i,
$prefix.$i.'@'.$postfix,
];
}


$this->data = $csv;
}


public function truncateTable()
{
$this->pdo->query('TRUNCATE TABLE `name`');
}


public function transactionSpeed()
{
$timer1 = microtime(true);
$this->pdo->beginTransaction();
$sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES (:first_name, :last_name)';
$sth = $this->pdo->prepare($sql);


foreach (\array_slice($this->data, 1) as $values) {
$sth->execute([
':first_name' => $values[1],
':last_name' => $values[2],
]);
}


// $timer2 = microtime(true);
// echo 'Prepare Time: '.($timer2 - $timer1).PHP_EOL;
// $timer3 = microtime(true);


if (!$this->pdo->commit()) {
echo "Commit failed\n";
}
$timer4 = microtime(true);
// echo 'Commit Time: '.($timer4 - $timer3).PHP_EOL;


return $timer4 - $timer1;
}


public function autoCommitSpeed()
{
$timer1 = microtime(true);
$sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES (:first_name, :last_name)';
$sth = $this->pdo->prepare($sql);
foreach (\array_slice($this->data, 1) as $values) {
$sth->execute([
':first_name' => $values[1],
':last_name' => $values[2],
]);
}
$timer2 = microtime(true);


return $timer2 - $timer1;
}


public function noBindAutoCommitSpeed()
{
$timer1 = microtime(true);


foreach (\array_slice($this->data, 1) as $values) {
$sth = $this->pdo->prepare("INSERT INTO `name` (`first_name`, `last_name`) VALUES ('{$values[1]}', '{$values[2]}')");
$sth->execute();
}
$timer2 = microtime(true);


return $timer2 - $timer1;
}


public function singleTransactionInsert()
{
$timer1 = microtime(true);
foreach (\array_slice($this->data, 1) as $values) {
$arr[] = "('{$values[1]}', '{$values[2]}')";
}
$sth = $this->pdo->prepare('INSERT INTO `name` (`first_name`, `last_name`) VALUES '.implode(', ', $arr));
$sth->execute();
$timer2 = microtime(true);


return $timer2 - $timer1;
}


public function singleTransactionInsertWithPlaceholders()
{
$placeholders = [];
$timer1 = microtime(true);
$sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES ';
foreach (\array_slice($this->data, 1) as $values) {
$placeholders[] = '(?, ?)';
$arr[] = $values[1];
$arr[] = $values[2];
}
$sql .= implode(', ', $placeholders);
$sth = $this->pdo->prepare($sql);
$sth->execute($arr);
$timer2 = microtime(true);


return $timer2 - $timer1;
}


public function singleTransactionInsertWithRollback()
{
$placeholders = [];
$timer1 = microtime(true);
$sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES ';
foreach (\array_slice($this->data, 1) as $values) {
$placeholders[] = '(?, ?)';
$arr[] = $values[1];
$arr[] = $values[2];
}
$sql .= implode(', ', $placeholders);
$this->pdo->beginTransaction();
$sth = $this->pdo->prepare($sql);
$sth->execute($arr);
$this->pdo->commit();
$timer2 = microtime(true);


return $timer2 - $timer1;
}
}


$s = new SpeedTestClass();
$s->createData();
$s->truncateTable();
echo "Time Spent for singleTransactionInsertWithRollback: {$s->singleTransactionInsertWithRollback()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for single Transaction Insert: {$s->singleTransactionInsert()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for single Transaction Insert With Placeholders: {$s->singleTransactionInsertWithPlaceholders()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for transaction: {$s->transactionSpeed()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for AutoCommit: {$s->noBindAutoCommitSpeed()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for autocommit with bind: {$s->autoCommitSpeed()}".PHP_EOL;
$s->truncateTable();

只包含两列的表的100,000个条目的结果如下所示

$ php data.php
Time Spent for singleTransactionInsertWithRollback: 0.75147604942322
Time Spent for single Transaction Insert: 0.67445182800293
Time Spent for single Transaction Insert With Placeholders: 0.71131205558777
Time Spent for transaction: 8.0056409835815
Time Spent for AutoCommit: 35.4979159832
Time Spent for autocommit with bind: 33.303519010544

这里有另一个解决这个问题的(微弱的)办法:

首先,需要使用 count ()对源数组(这里是 $aData)的数据进行计数。然后使用 array _ fill ()生成一个新的数组,其中包含与源数组一样多的条目,每个条目的值为“(?,?)"(占位符的数量取决于您使用的字段; 这里: 2)。然后生成的数组需要内爆,并使用逗号作为粘合剂。 在 foreach 循环中,需要根据所使用的占位符数量(占位符数量 * 当前数组索引 + 1)生成另一个索引。在每个绑定值之后,您需要向生成的索引添加1。

$do = $db->prepare("INSERT INTO table (id, name) VALUES ".implode(',', array_fill(0, count($aData), '(?,?)')));


foreach($aData as $iIndex => $aValues){
$iRealIndex = 2 * $iIndex + 1;
$do->bindValue($iRealIndex, $aValues['id'], PDO::PARAM_INT);
$iRealIndex = $iRealIndex + 1;
$do->bindValue($iRealIndex, $aValues['name'], PDO::PARAM_STR);
}


$do->execute();

像这样的东西怎么样:

        if(count($types_of_values)>0){
$uid = 1;
$x = 0;
$sql = "";
$values = array();
foreach($types_of_values as $k=>$v){
$sql .= "(:id_$k,:kind_of_val_$k), ";
$values[":id_$k"] = $uid;
$values[":kind_of_val_$k"] = $v;
}
$sql = substr($sql,0,-2);
$query = "INSERT INTO table (id,value_type) VALUES $sql";
$res = $this->db->prepare($query);
$res->execute($values);
}

其背后的思想是循环遍历数组值,为准备好的语句占位符向每个循环添加“ id 号”,同时为绑定参数向数组添加值。如果不喜欢使用数组中的“ key”索引,可以在循环中添加 $i = 0和 $i + + 。在本例中,两种方法都可以工作,即使您有具有命名键的关联数组,只要键是唯一的,它仍然可以工作。只要做一点工作,对于嵌套数组来说也是可以的。.

* * 注意,subr 去掉 $sql 变量的最后一个空格和逗号,如果没有空格,则需要将其更改为 -1而不是 -2。