【问题标题】:php inserting data in mysql from json runs too slowlyphp从json向mysql中插入数据运行速度太慢
【发布时间】:2015-04-09 04:31:33
【问题描述】:

我有以下代码来读取 JSON 并将结果存储在 DDBB 中。
该代码有效,但仅插入 400 条记录需要一分钟多的时间。
如果我打开 json,它会加载得非常快。
我做错了什么?

    $db = new PDO('', '', '');
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    if(tableExists($db, 'locations') == 1)
    {
        $sql=$db->prepare("DROP TABLE locations");
        $sql->execute();
    }
    $sql ="CREATE TABLE `locations` (id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, evid INT(6) NOT NULL, place VARCHAR(150), country VARCHAR(150), reg_date TIMESTAMP)" ;
    $db->exec($sql);
    $json = file_get_contents('thejson.php');
    $data = array();
    $data = json_decode($json); 
    foreach ($data as $key => $object) 
    {
        if(is_object($object))
        {
            $id = $object->id;
            $place = $object->name;
            $country = substr(strrchr($object->name, "-"), 2);
            $stmt = $db->prepare("INSERT INTO `locations` (evid, place, country) VALUES (:evid, :places, :country)");
            $stmt->bindValue(':evid', $id, PDO::PARAM_INT);
            $stmt->bindValue(':places', $place, PDO::PARAM_STR);  
            $stmt->bindValue(':country', $country, PDO::PARAM_STR);      
            $stmt->execute();
        }
    }

【问题讨论】:

  • 为什么不在单个查询中插入完整的数组,而不是为每一行运行一个循环
  • 为了提高速度,您不应该在开始时创建密钥。插入没有键的记录。在插入过程结束时创建密钥。
  • Pavan Jiwnani,你到底是什么意思? Franz Holzinger,我也不明白你的意思,你能给我举个例子吗?

标签: php mysql json pdo


【解决方案1】:

所以我首先要尝试的两件事是将准备移动到循环之外,并将其包装在事务中:

try {
    $db->beginTransaction();
    $stmt = $db->prepare("INSERT INTO `locations` (evid, place, country) VALUES (:evid, :places, :country)");

    foreach ($data as $key => $object) 
    {
        if(is_object($object))
        {
            $id = $object->id;
            $place = $object->name;
            $country = substr(strrchr($object->name, "-"), 2);

            $stmt->bindValue(':evid', $id, PDO::PARAM_INT);
            $stmt->bindValue(':places', $place, PDO::PARAM_STR);  
            $stmt->bindValue(':country', $country, PDO::PARAM_STR);      
            $stmt->execute();
        }
    }

    $db->commit();

} catch (Exception $e) {
    $db->rollBack();
    throw $e;
}

您可以做的另一件事是尝试使用bindParam 通过引用绑定变量 - 这样您只需在开始时对每个变量名调用一次bindParam,然后只需在每个变量上覆盖这些变量的值迭代并调用执行。

try {
    $db->beginTransaction();
    $stmt = $db->prepare("INSERT INTO `locations` (evid, place, country) VALUES (:evid, :places, :country)");
     $id = 0;
     $place = '';
     $country = '';

     $stmt->bindParam(':evid', $id, PDO::PARAM_INT);
     $stmt->bindParam(':places', $place, PDO::PARAM_STR);  
     $stmt->bindParam(':country', $country, PDO::PARAM_STR); 

    foreach ($data as $key => $object) 
    {
        if(is_object($object))
        {
            $id = $object->id;
            $place = $object->name;
            $country = substr(strrchr($object->name, "-"), 2);
            $stmt->execute();
        }
    }

    $db->commit();

} catch (Exception $e) {
    $db->rollBack();
    throw $e;
}

与此类似,您可以通过 execute 传递值,而不是调用 bind*

try {
    $db->beginTransaction();
    $stmt = $db->prepare("INSERT INTO `locations` (evid, place, country) VALUES (:evid, :places, :country)");

    foreach ($data as $key => $object) 
    {
        if(is_object($object))
        {
            $params = array(
                ':id' => $object->id,
                ':places' => $object->name,
                ':country' => substr(strrchr($object->name, "-"), 2)
            );

            $stmt->execute($params);
        }
    }

    $db->commit();

} catch (Exception $e) {
    $db->rollBack();
    throw $e;
}

我怀疑使用事务会给您带来性能提升,但我不知道切换绑定方法之间会有很大差异。

您最好的选择可能是按照@PavanJiwnani 的建议在一个查询中插入所有记录:

// first we need to compile a structure of only items 
// we will insert with the values properly transformed

$insertData = array_map(function ($object) {
     if (is_object($object)) {
        return array(
            $object->id,
            $object->name,
            substr(strrchr($object->name, "-"), 2)
        );
     } else {
       return false;
     }
}, $data);

// filter out the FALSE values
$insertData = array_filter($insertData);

// get the number of records we have to insert
$nbRecords = count($insertData);

// $records is an array containing a (?,?,?) 
// for each item we want to insert
$records = array_fill(0, $nbRecords, '(?,?,?)');

// now now use sprintf and implode to generate the SQL like:
// INSERT INTO `locations` (evid, place, country) VALUES (?,?,?),(?,?,?),(?,?,?),(?,?,?)
$sql = sprintf(
    'INSERT INTO `locations` (evid, place, country) VALUES %s',   
    implode(',', $records)
);

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

// Now we need to flatten our array of insert values as that is what 
// will be expected by execute()
$params = array();
foreach ($insertData as $datum) {
   $params = array_merge($params, $datum);
}

// and finally we attempt to execute
$stmt->execute($params);

【讨论】:

  • 最后一个选项就像一个魅力,非常感谢您的帮助。现在它对我来说看起来很明显和聪明,但我无法找到解决方案。为了将对象转换为数组,只需要再添加一件事“get_object_vars”。感谢您的帮助!
  • 嗯..你为什么需要get_object_vars?致电array_map
  • 如果我像你一样使用它,PHP 会启动一个错误,抱怨 array_map 的第二个参数不是一个数组(实际上它是一个对象)
  • 哦..我以为这是一个对象数组,即。 [{"name":"somename","id":1}]...如果是这种情况,您应该使用json_decode($jsonString, true),那么它将解码为关联数组,从而无需使用get_object_vars(我个人觉得很脏,应该避免使用)。
【解决方案2】:

影响数据库性能的因素很多,请提供数据库系统、PHP版本及相关硬件的详细信息。

瓶颈可能在:

file_get_contents('thejson.php')

如果从远程主机获取 JSON 内容,即 DB 运行正常,则网络慢。

您可能还想考虑搬家:

$stmt = $db->prepare("INSERT INTO `locations` (evid, place, country) VALUES (:evid, :places, :country)");

退出foreach循环。

【讨论】:

  • 我已将准备移出循环,但这并不能解决问题。如果我直接打开 json.php,它会快速提供 json,所以我不确定这是否是问题所在。服务器配置为Linux:PHP 5.3.10,MySQL 5.5。
【解决方案3】:

尝试以毫秒为单位回显时间戳,以查看运行缓慢的内容。可能执行 400 个插入查询(包括打开/关闭连接)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-08-04
    • 2020-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多