使用php pdo和准备好的语句分批拆分大插入查询

使用php pdo和准备好的语句分批拆分大插入查询,php,mysql,pdo,Php,Mysql,Pdo,我使用以下函数将多条记录插入数据库。此函数使用单个查询将记录添加到mysql数据库中 function addHostBulk($value, $label, $group, $type, $user) { $res = array("type" => intval($type), "value" => $value, "label" => $label, "group_id" => $group, "owner" => intval($user['id']

我使用以下函数将多条记录插入数据库。此函数使用单个查询将记录添加到mysql数据库中

function addHostBulk($value, $label, $group, $type, $user)
{
    $res = array("type" => intval($type), "value" => $value, "label" => $label, "group_id" => $group, "owner" => intval($user['id']));

        $hosts = $common->cidrToRange($res['value'], $user); //Returns array with all hosts from this CIDR range /array('hosts' => array('1.2.3.4', '1.2.3.5.', ...)/
        $final_count = count($hosts['hosts']);

        $this->db->beginTransaction();
        $query = ("INSERT INTO hosts (`id`, `type`, `value`, `label`, `group_id`, `owner`) VALUES ");
        $qPart = array_fill(0, count($hosts['hosts']), "(?, ?, ?, ?, ?, ?)");
        $query .=  implode(",",$qPart);

        $statement = $this->db->prepare($query);

        $i = 1;
        foreach($hosts['hosts'] as $host){
                $statement->bindParam($i++, $common->uuid(), PDO::PARAM_STR);
                $statement->bindParam($i++, $res['type'], PDO::PARAM_STR);
                $statement->bindParam($i++, $host, PDO::PARAM_STR);
                $statement->bindParam($i++, $res['label'], PDO::PARAM_STR);
                $statement->bindParam($i++, $res['group_id'], PDO::PARAM_STR);
                $statement->bindParam($i++, $res['owner'], PDO::PARAM_STR);
                unset($host);
        }

        $statement->execute();
        $this->db->commit();

    return true;
}
代码运行正常,但是如果我尝试一次插入大约30000条记录,我达到了mysql的限制,出现了异常:“一般错误:1390 Prepared语句包含太多占位符”

据我所知,我可以通过将查询拆分为小批量来解决这个问题。将其拆分为1000个批次就可以了(一个查询有6个占位符x 1000条记录=6k占位符。Mysql限制为~65k占位符)


有谁能帮我拆分它吗?

不要试图创建一个庞大的SQL语句并绑定数千个变量! 这在很多方面都是错误的:绑定变量的数量是有限的,SQL语句的大小也被限制在1MB左右,等等

相反,请将代码更改为使用单个静态INSERT语句,如下所示:

$query = "INSERT INTO hosts (`id`, `type`, `value`, `label`, `group_id`, `owner`)"
       . "VALUES (?, ?, ?, ?, ?, ?)";
$this->db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);  # important!
$this->db->beginTransaction();
$statement = $this->db->prepare($query);
foreach($hosts['hosts'] as $host) {
     # bind 6 variables...
     $statement->execute();
}
$this->db->commit();
然后像这样做:

$query = "INSERT INTO hosts (`id`, `type`, `value`, `label`, `group_id`, `owner`)"
       . "VALUES (?, ?, ?, ?, ?, ?)";
$this->db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);  # important!
$this->db->beginTransaction();
$statement = $this->db->prepare($query);
foreach($hosts['hosts'] as $host) {
     # bind 6 variables...
     $statement->execute();
}
$this->db->commit();

这段代码在一个事务中可以处理数百万行,并且应该运行得非常快。

您可以执行类似的操作,在foreach中输入条件以检查是否有1000多条记录,然后插入前1000条,并循环其他类似的记录

    $i = 1;
    $totalCount = 0;
    foreach($hosts['hosts'] as $host){

            $statement->bindParam($i++, $common->uuid(), PDO::PARAM_STR);
            $statement->bindParam($i++, $res['type'], PDO::PARAM_STR);
            $statement->bindParam($i++, $host, PDO::PARAM_STR);
            $statement->bindParam($i++, $res['label'], PDO::PARAM_STR);
            $statement->bindParam($i++, $res['group_id'], PDO::PARAM_STR);
            $statement->bindParam($i++, $res['owner'], PDO::PARAM_STR);
            unset($host);

            if($i==1000)
            {
               $statement->execute();
               $this->db->commit();
               $i=0;
            }

    }

    //check for last time if any records left then you have to insert those records also

    if($i<1000)
    {
         $statement->execute();
         $this->db->commit();

    }
$i=1;
$totalCount=0;
foreach($hosts['hosts']作为$host){
$statement->bindParam($i++,$common->uuid(),PDO::PARAM_STR);
$statement->bindParam($i++,$res['type'],PDO::PARAM_STR);
$statement->bindParam($i++,$host,PDO::PARAM_STR);
$statement->bindParam($i++,$res['label'],PDO::PARAM_STR);
$statement->bindParam($i++,$res['group_id',PDO::PARAM_STR);
$statement->bindParam($i++,$res['owner',PDO::PARAM_STR);
unset(主机);
如果($i==1000)
{
$statement->execute();
$this->db->commit();
$i=0;
}
}
//检查最后一次是否有任何记录,然后您还必须插入这些记录
如果($iexecute();
$this->db->commit();
}
函数addHostBulk($value、$label、$group、$type、$user)
{
$res=array(“type”=>intval($type),“value”=>value,“label”=>label,“group\u id”=>group,“owner”=>intval($user['id']);
$hosts=$common->CIDRORAGE($res['value'],$user);//返回包含此CIDR范围/数组中所有主机的数组('hosts'=>array('1.2.3.4','1.2.3.5',…)/
$totalHosts=count($hosts['hosts']);
$batchSize=1000;
对于($idx=0;$idx*$batchSize<$totalHosts;$idx++){
$hostsparial=array_slice($hosts['hosts'],$idx*$batchSize,$batchSize);
$this->db->beginTransaction();
$query=(“插入主机(`id`、`type`、`value`、`label`、`group\u id`、`owner`)值”);
$qPart=数组填充(0,计数($hostsparial),“(?,?,?,,?,,?)”;
$query.=内爆(“,”,$qPart);
$statement=$this->db->prepare($query);
$i=1;
foreach($hostsPartial作为$host){
$statement->bindParam($i++,$common->uuid(),PDO::PARAM_STR);
$statement->bindParam($i++,$res['type'],PDO::PARAM_STR);
$statement->bindParam($i++,$host,PDO::PARAM_STR);
$statement->bindParam($i++,$res['label'],PDO::PARAM_STR);
$statement->bindParam($i++,$res['group_id',PDO::PARAM_STR);
$statement->bindParam($i++,$res['owner',PDO::PARAM_STR);
unset(主机);
}
$statement->execute();
$this->db->commit();
}
返回true;
}

为什么你甚至会考虑插入这么多的副本?我看到了DE规范化,但是这很荒谬。你不需要“拆分”查询,你需要一个数据库重新设计。start@Sorin..重复是什么意思?我只是在每个查询中插入许多行。它们不是重复的。它们有相同的字段,但不是t他使用了相同的值!我已经向您指出了一个链接,您应该在响应之前阅读该链接。您正在插入重复项,所有行的值都相同,只有两个除外(实际上是一个,在本例中UUID可能没有用)这与第二个正常形式相反。我怀疑有任何真正的理由来做你正在做的事情,我敢打赌你的“设计”是错误的。@Sorin,请仔细检查代码。我正在插入不同的主机(ip地址)从cidrToRange函数计算。是的,这是一个不同的字段…其余的都是重复的-读链接,看在上帝的份上sake@downvoter当前位置如果能解释一下这个答案的错误,那就太好了