MySQLi:使用一个准备好的语句插入多行
Posted
技术标签:
【中文标题】MySQLi:使用一个准备好的语句插入多行【英文标题】:MySQLi : Inserting multiple rows with one prepared statement 【发布时间】:2013-02-13 18:38:03 【问题描述】:我制作了一个脚本,它创建一个原始查询字符串,然后在一个语句中插入数百行。它有效,但不提供准备好的语句所提供的保护。然后我修改了我的脚本以添加准备好的语句。它可以工作,但是速度要慢得多。与原始查询脚本相比,带有准备好的语句的脚本插入行的时间要长得多,因为脚本一次运行一行,而不是一次插入数百行。
这是准备好的语句代码的sn-p:
for( $j = 0; $j < $abilitiesMax - 2; $j++ )
$stmtAbility->bind_param('iiiii', $abilityArray[$i]["match_id"] , $abilityArray[$i]["player_slot"],
$abilityArray[$i][$j]["ability"], $abilityArray[$i][$j]["time"], $abilityArray[$i][$j]["level"] );
if( !($stmtAbility->execute()) )
echo "<p>$db->error</p>";
echo "<p>ERROR: when trying to insert abilities query</p>";
它完成了工作,但只有在数百次插入之后。有没有办法将列表或数组绑定到 bind_param() 参数,然后只运行 $stmtAbility->execute one time 或其他可以提高性能的方法。
对不起,如果之前有人问过并回答过。我环顾了一会儿,发现了一些类似的问题,但没有一个能明确回答我的要求。
【问题讨论】:
至少高度相关:***.com/questions/4659317/bulk-parameterized-inserts? 您可能希望在循环之前绑定参数,然后在其中设置绑定变量的值。 @MichaelRushton 我在想类似的事情,尽管我怀疑对bind_param
的调用是瓶颈。大部分开销可能在execute()
。
只要您的常规查询格式正确,它就和准备好的一样安全。
感谢 cmets 和链接。
【参考方案1】:
可以通过动态构建批量插入语句查询来准备它,但这需要一些技巧。最重要的位是使用str_pad()
构造一个可变长度的查询字符串,并使用call_user_func_array()
调用bind_param()
并带有可变数量的参数。
function insertBulkPrepared($db, $table, $fields, $types, $values)
$chunklength = 500;
$fieldcount = count($fields);
$fieldnames = '`'.join('`, `', $fields).'`';
$prefix = "INSERT INTO `$table` ($fieldnames) VALUES ";
$params = '(' . str_pad('', 3*$fieldcount - 2, '?, ') . '), ';
$inserted = 0;
foreach (array_chunk($values, $fieldcount*$chunklength) as $group)
$length = count($group);
if ($inserted != $length)
if ($inserted) $stmt->close();
$records = $length / $fieldcount;
$query = $prefix . str_pad('', 3*$length + 2*($records - 1), $params);
#echo "\n<br>Preparing '" . $query . "'";
$stmt = $db->prepare($query);
if (!$stmt) return false;
$binding = str_pad('', $length, $types);
$inserted = $length;
array_unshift($group, $binding);
#echo "\n<br>Binding " . var_export($group, true);
$bound = call_user_func_array(array($stmt, 'bind_param'), $group);
if (!$bound) return false;
if (!$stmt->execute()) return false;
if ($inserted) $stmt->close();
return true;
此函数将您的$db
作为mysqli
实例、表名、字段名数组和对值的引用的平面数组。它在每个查询中最多插入 500 条记录,并尽可能重复使用准备好的语句。如果所有插入都成功,则返回true
,如果其中任何一个插入失败,则返回false
。注意事项:
$values
的长度不是$fields
长度的偶数倍,则最终块可能会在准备阶段失败。
同样,$types
参数的长度在大多数情况下应该与$fields
的长度匹配,尤其是当它们中的一些不同时。
它不区分三种失败方式。它也不会跟踪成功插入的次数,也不会在出错后尝试继续。
定义此函数后,您的示例代码可以替换为:
$inserts = array();
for ($j = 0; $j < $abilitiesMax - 2; $j++)
$inserts[] = &$abilityArray[$i]['match_id'];
$inserts[] = &$abilityArray[$i]['player_slot'];
$inserts[] = &$abilityArray[$i][$j]['ability'];
$inserts[] = &$abilityArray[$i][$j]['time'];
$inserts[] = &$abilityArray[$i][$j]['level'];
$fields = array('match_id', 'player_slot', 'ability', 'time', 'level');
$result = insertBulkPrepared($db, 'abilities', $fields, 'iiiii', $inserts);
if (!$result)
echo "<p>$db->error</p>";
echo "<p>ERROR: when trying to insert abilities query</p>";
这些 & 符号很重要,因为 mysqli_stmt::bind_param
需要引用,而在最新版本的 php 中 call_user_func_array
没有提供这些引用。
您没有给我们原始准备好的语句,因此您可能需要调整表和字段名称。看起来您的代码也位于$i
的循环内;在这种情况下,只有for
循环需要在外循环内。如果您将其他行放在循环之外,您将使用更多内存来构造$inserts
数组,以换取更高效的批量插入。
也可以重写insertBulkPrepared()
以接受多维数组,从而消除一个潜在错误源,但这需要在分块后将数组展平。
【讨论】:
好用的功能!如果你将函数 params: $fields, $types & $values 组合成一个参数 $records 来读取 key => value 数组,也许你可以做得更好。以上是关于MySQLi:使用一个准备好的语句插入多行的主要内容,如果未能解决你的问题,请参考以下文章