我正在使用插入命令通过 PHP 将大型数据集传递到 MySQL 表中,我想知道是否可以通过查询一次插入大约 1000 行,而不是将每个值附加到一英里长的字符串的末尾和然后执行它。我正在使用 CodeIgniter 框架,因此我也可以使用它的功能。
13 回答
在 MySQL 中,将一条语句与多行组合起来比每行INSERT
一条语句要快得多。INSERT
也就是说,听起来您可能会在 PHP 中遇到字符串处理问题,这实际上是一个算法问题,而不是语言问题。基本上,在处理大字符串时,您希望尽量减少不必要的复制。首先,这意味着您要避免串联。构建大字符串(例如一次插入数百行)的最快和最节省内存的方法是利用implode()
函数和数组分配。
$sql = array();
foreach( $data as $row ) {
$sql[] = '("'.mysql_real_escape_string($row['text']).'", '.$row['category_id'].')';
}
mysql_query('INSERT INTO table (text, category) VALUES '.implode(',', $sql));
这种方法的优点是您不必复制和重新复制到目前为止与每个连接一起组装的 SQL 语句。相反,PHP在语句中执行此操作一次。implode()
这是一个很大的胜利。
如果您有很多列要放在一起,并且一个或多个很长,您还可以构建一个内部循环来执行相同的操作,并使用implode()
将 values 子句分配给外部数组。
CodeIgniter 现在支持多个插入/批量插入。
$data = array(
array(
'title' => 'My title' ,
'name' => 'My Name' ,
'date' => 'My date'
),
array(
'title' => 'Another title' ,
'name' => 'Another Name' ,
'date' => 'Another date'
)
);
$this->db->insert_batch('mytable', $data);
// Produces: INSERT INTO mytable (title, name, date) VALUES ('My title', 'My name', 'My date'), ('Another title', 'Another name', 'Another date')
您可以使用 mysqli_stmt 类准备查询以插入一行,然后遍历数据数组。就像是:
$stmt = $db->stmt_init();
$stmt->prepare("INSERT INTO mytbl (fld1, fld2, fld3, fld4) VALUES(?, ?, ?, ?)");
foreach($myarray as $row)
{
$stmt->bind_param('idsb', $row['fld1'], $row['fld2'], $row['fld3'], $row['fld4']);
$stmt->execute();
}
$stmt->close();
其中“idsb”是您要绑定的数据类型(int、double、string、blob)。
PHP 5 中的 mysqli 是一个具有一些良好功能的对象,可以让您加快上述答案的插入时间:
$mysqli->autocommit(FALSE);
$mysqli->multi_query($sqlCombined);
$mysqli->autocommit(TRUE);
插入多行时关闭自动提交会大大加快插入速度,所以关闭它,然后按上面提到的那样执行,或者只是制作一个字符串(sqlCombined),它是由分号分隔的许多插入语句,多查询可以很好地处理它们。
你总是可以使用mysql的LOAD DATA
:
LOAD DATA LOCAL INFILE '/full/path/to/file/foo.csv' INTO TABLE `footable` FIELDS TERMINATED BY ',' LINES TERMINATED BY '\r\n'
做批量插入而不是使用一堆INSERT
语句。
好吧,您不想执行 1000 次查询调用,但这样做很好:
$stmt= array( 'array of statements' );
$query= 'INSERT INTO yourtable (col1,col2,col3) VALUES ';
foreach( $stmt AS $k => $v ) {
$query.= '(' .$v. ')'; // NOTE: you'll have to change to suit
if ( $k !== sizeof($stmt)-1 ) $query.= ', ';
}
$r= mysql_query($query);
根据您的数据源,填充数组可能就像打开文件并通过file()
.
$query= array();
foreach( $your_data as $row ) {
$query[] = '("'.mysql_real_escape_string($row['text']).'", '.$row['category_id'].')';
}
mysql_query('INSERT INTO table (text, category) VALUES '.implode(',', $query));
您可以在 codeigniter 中通过多种方式来实现,例如
第一个循环
foreach($myarray as $row)
{
$data = array("first"=>$row->first,"second"=>$row->sec);
$this->db->insert('table_name',$data);
}
第二——通过插入批次
$data = array(
array(
'first' => $myarray[0]['first'] ,
'second' => $myarray[0]['sec'],
),
array(
'first' => $myarray[1]['first'] ,
'second' => $myarray[1]['sec'],
),
);
$this->db->insert_batch('table_name', $data);
第三种方式——通过多值传递
$sql = array();
foreach( $myarray as $row ) {
$sql[] = '("'.mysql_real_escape_string($row['first']).'", '.$row['sec'].')';
}
mysql_query('INSERT INTO table (first, second) VALUES '.implode(',', $sql));
虽然现在回答这个问题为时已晚。这是我的回答。
如果您使用的是 CodeIgniter,那么您可以使用在 query_builder 类中定义的内置方法。
$this->db->insert_batch()
根据您提供的数据生成插入字符串,然后运行查询。您可以将数组或对象传递给函数。下面是一个使用数组的例子:
$data = array(
array(
'title' => 'My title',
'name' => 'My Name',
'date' => 'My date'
),
array(
'title' => 'Another title',
'name' => 'Another Name',
'date' => 'Another date'
)
);
$this->db->insert_batch('mytable', $data);
// Produces: INSERT INTO mytable (title, name, date) VALUES ('My title', 'My name', 'My date'), ('Another title', 'Another name', 'Another date')
第一个参数将包含表名,第二个参数是值的关联数组。
您可以在此处找到有关 query_builder 的更多详细信息
我创建了一个执行多行的类,使用如下:
$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;
/**
* Create a PDOMultiLine Insert object.
*
* @param PDO $pdo The PDO connection
* @param type $tableName The table name
* @param type $fieldsAsArray An array of the fields being inserted
* @param type $bigInsertCount How many rows to collect before performing an insert.
*/
function __construct(PDO $pdo, $tableName, $fieldsAsArray, $bigInsertCount = 100) {
$this->_numberOfFields = count($fieldsAsArray);
$insertIntoPortion = "REPLACE 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;
}
}
在 codeigniter 中使用 insert batch 插入多行数据。
$this->db->insert_batch('tabname',$data_array); // $data_array holds the value to be inserted
我不得不在一个表中插入超过 14000 行,并发现使用 Mysqli 准备好的语句的每一行花费了十多分钟,而使用相同 Mysqli 准备好的语句的字符串参数解包参数不到 10 秒。我的数据非常重复,因为它是 id 的倍数和一个常量整数。
10分钟代码:
$num = 1000;
$ent = 4;
$value = ['id' => 1,
'id' => 2,
'id' => 3,
'id' => 4,
'id' => 5,
'id' => 6,
'id' => 7,
'id' => 8,
'id' => 9,
'id' => 10,
'id' => 11,
'id' => 12,
'id' => 13,
'id' => 14];
$cnt = 0;
$query = "INSERT INTO table (col1, col2) VALUES (?,?)";
$stmt = $this->db->prepare($query);
$stmt->bind_param('ii', $arg_one,$arg_two);
foreach ($value as $k => $val) {
for ($i=0; $i < $num; $i++) {
$arg_one = $k;
$arg_two = $ent;
if($stmt->execute()) {
$cnt++;
}
}
}
10秒代码:
$ent = 4;
$num = 1000;
$value = ['id' => 1,
'id' => 2,
'id' => 3,
'id' => 4,
'id' => 5,
'id' => 6,
'id' => 7,
'id' => 8,
'id' => 9,
'id' => 10,
'id' => 11,
'id' => 12,
'id' => 13,
'id' => 14];
$newdat = [];
foreach ($value as $k => $val) {
for ($i=0; $i < $num; $i++) {
$newdat[] = $val;
$newdat[] = $ent;
}
}
// create string of data types
$cnt = count($newdat);
$param = str_repeat('i',$cnt);
// create string of question marks
$rec = (count($newdat) == 0) ? 0 : $cnt / 2 - 1;
$id_q = str_repeat('(?,?),', $rec) . '(?,?)';
// insert
$query = "INSERT INTO table (col1, col2) VALUES $id_q";
$stmt = $db->prepare($query);
$stmt->bind_param($param, ...$newdat);
$stmt->execute();
我创建了这个简单的功能,你们可以轻松使用。您需要针对插入数据、数据数组传递表名($tbl)
、表字段。($insertFieldsArr)
($arr)
insert_batch('table',array('field1','field2'),$dataArray);
function insert_batch($tbl,$insertFieldsArr,$arr){ $sql = array();
foreach( $arr as $row ) {
$strVals='';
$cnt=0;
foreach($insertFieldsArr as $key=>$val){
if(is_array($row)){
$strVals.="'".mysql_real_escape_string($row[$cnt]).'\',';
}
else{
$strVals.="'".mysql_real_escape_string($row).'\',';
}
$cnt++;
}
$strVals=rtrim($strVals,',');
$sql[] = '('.$strVals.')';
}
$fields=implode(',',$insertFieldsArr);
mysql_query('INSERT INTO `'.$tbl.'` ('.$fields.') VALUES '.implode(',', $sql));
}