我有一个PHP函数,可以批量插入MYSQL表。该函数将输入参数作为数组,然后循环通过数组构建插入查询,如下所示:
public function batchInsert($values){
$nbValues = count($values);
$sql = 'INSERT INTO vouchers (`code`,`pin`,`owner_id`,`value`,`description`,`expire_date`,`lifetime`) VALUES ';
for ($i=0; $i < $nbValues; $i++) {
$sql .= '(:col1_'.$i.', :col2_'.$i.', :col3_'.$i.', :col4_'.$i.', :col5_'.$i.', :col6_'.$i.', :col7_'.$i.')';
if ($i !== ($nbValues-1))
$sql .= ',';
}
$command = Yii::app()->db->createCommand($sql);
for ($i=0; $i < $nbValues; $i++) {
$command->bindParam(':col1_'.$i, $values[$i]['code'], PDO::PARAM_STR);
$command->bindValue(':col2_'.$i, sha1($values[$i]['pin']), PDO::PARAM_STR);
$command->bindParam(':col3_'.$i, $values[$i]['owner_id'], PDO::PARAM_INT);
$command->bindParam(':col4_'.$i, $values[$i]['value'], PDO::PARAM_INT);
$command->bindParam(':col5_'.$i, $values[$i]['description'], PDO::PARAM_STR);
$command->bindParam(':col6_'.$i, $values[$i]['expire_date'], PDO::PARAM_STR);
$command->bindParam(':col7_'.$i, $values[$i]['lifetime'], PDO::PARAM_INT);
}
return $command->execute();
}
如果输入数组有1K元素,则构建此sql查询将花费相当长的时间。我相信这是由每个循环后重构$ sql变量的方式引起的。有没有更好的方法可以建议我优化它?谢谢!
P / S:在批量插入结束时,我需要将所有生成的凭证导出到Excel文件。因此,如果我构建了一个单一查询,并且查询成功,则调用导出函数。通过进行许多单独的插入,我无法跟踪插入哪一个以及哪一个不插入(例如,凭证代码是唯一的,随机生成并且可能有碰撞的机会)。这就是为什么我需要一个查询(或者我错了?)。
答案 0 :(得分:4)
考虑执行单个插入,而不是构建一个巨大的字符串,而是利用预先准备好的语句
public function batchInsert($values){
$nbValues = count($values);
$sql = 'INSERT INTO vouchers (`code`,`pin`,`owner_id`,`value`,`description`,`expire_date`,`lifetime`)
VALUES (:col1, :col2, :col3, :col4, :col5, :col6, :col7)';
$command = Yii::app()->db->createCommand($sql);
for ($i=0; $i < $nbValues; $i++) {
$command->bindParam(':col1', $values[$i]['code'], PDO::PARAM_STR);
$command->bindValue(':col2', sha1($values[$i]['pin']), PDO::PARAM_STR);
$command->bindParam(':col3', $values[$i]['owner_id'], PDO::PARAM_INT);
$command->bindParam(':col4', $values[$i]['value'], PDO::PARAM_INT);
$command->bindParam(':col5', $values[$i]['description'], PDO::PARAM_STR);
$command->bindParam(':col6', $values[$i]['expire_date'], PDO::PARAM_STR);
$command->bindParam(':col7', $values[$i]['lifetime'], PDO::PARAM_INT);
$command->execute();
}
}
所以你只准备一次短插入语句,然后在循环中绑定/执行
答案 1 :(得分:1)
让我们先定义您的要求:
第一个问题是你在这里处理数据库。现代MySQL使用InnoDB
作为存储引擎 - 它是一个事务引擎。
默认情况下,PDO
使用名为auto-commit
的内容。
这对你来说意味着什么?基本上,这意味着事务引擎将强制硬盘驱动器在它告诉您写入之前真正写入记录。像MyISAM或NoSQL这样的引擎不会这样做。他们只会让操作系统担心写入操作系统,操作系统只会将应该写入磁盘的信息排队。 磁盘速度非常慢,因此操作系统会尝试进行补偿,而某些磁盘甚至还有缓存存储大量临时数据的缓存。
但是,除非信息 真正写入磁盘,否则它不会保存,因为它可能会丢失。这是数据库中D
的{{1}}部分 - 数据持久,这是永久存储设备上的数据。这就是MySQL和其他事务数据库速度慢的原因 - 因为硬盘驱动器设备很慢。机械硬盘驱动器能够执行每秒100到300次写入(我们称之为ACID
或每秒输入输出操作)。这像蜗牛一样缓慢。
因此,IOPS
默认情况下会强制每个查询都是一个事务。这意味着您执行的每个查询都会占用1 PDO
,而您只有一些查询。因此,当您运行1000个插入时,如果一切都很好并且您确实有300 IOPS
可用,那么您的插入将需要一段时间。如果他们失败并且您必须重试它们,那么它会更长,因为它会持续更长时间。
那么你能做些什么来加快速度呢?你做了两件事。
1)当您完成时,使用IOPS
方法PDO
和beginTransaction
将多个插入包装到单个事务中。这使得硬盘驱动器使用1 commit
写入多条记录。如果将所有1000个插入包装到单个事务中,则很可能写得非常快。即使磁盘具有低IOPS
,它们也会包含很多带宽,因此它们可以一次性消耗所有1000个插入
2)确保所有插入都成功。这意味着一旦插入所有内容,您可能应该在游戏的稍后阶段生成您的优惠券代码。请记住,如果某个事务中的单个查询失败 - 所有这些查询都失败了(IOPS
A
- Atomicity)。
基本上,我在这里要强调的是Mark Baker发布了一个很好的答案,你很可能会稍微修改一下你的逻辑。准备语句一次,执行多次。但是,在事务中执行 wrap 多次调用 - 这将使其快速实现。
答案 2 :(得分:0)
我所做的是将字符串更改为数组,然后在最后一步内爆:
public function batchInsert($values){
$nbValues = count($values);
$sql = array();
for ($i=0; $i < $nbValues; $i++) {
$sql[] = '(:col1_'.$i.', :col2_'.$i.', :col3_'.$i.', :col4_'.$i.', :col5_'.$i.', :col6_'.$i.', :col7_'.$i.')';
}
$command = Yii::app()->db->createCommand('INSERT INTO vouchers (`code`,`pin`,`owner_id`,`value`,`description`,`expire_date`,`lifetime`) VALUES (' . implode('),(',$sql) . ')');
for ($i=0; $i < $nbValues; $i++) {
$command->bindParam(':col1_'.$i, $values[$i]['code'], PDO::PARAM_STR);
$command->bindValue(':col2_'.$i, sha1($values[$i]['pin']), PDO::PARAM_STR);
$command->bindParam(':col3_'.$i, $values[$i]['owner_id'], PDO::PARAM_INT);
$command->bindParam(':col4_'.$i, $values[$i]['value'], PDO::PARAM_INT);
$command->bindParam(':col5_'.$i, $values[$i]['description'], PDO::PARAM_STR);
$command->bindParam(':col6_'.$i, $values[$i]['expire_date'], PDO::PARAM_STR);
$command->bindParam(':col7_'.$i, $values[$i]['lifetime'], PDO::PARAM_INT);
}
return $command->execute();
}