【问题标题】:PHP: insert 10.000 rows to mysql in one go performancePHP:一次性向 mysql 插入 10.000 行性能
【发布时间】:2014-05-12 22:55:10
【问题描述】:

我需要读取约 10.000 行的 excel 文件并将它们保存到 MySQL 中的表中。我使用的方法是创建一个foreach() 循环并在其中准备、绑定和执行每一行。

执行时间大约是 130 秒,我认为这很糟糕。那是在本地主机上,所以当脚本实时运行(共享主机)时,执行时间肯定会更长。

这是代码

ini_set('max_execution_time', 300);

$time_start = microtime(true);

$user = 'root';
$pass = '';
$driver_options = array(
    PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'",
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
);
try {
    $dbh = new PDO('mysql:host=127.0.0.1;dbname=excel', $user, $pass, $driver_options);
} catch (PDOException $e) {
    print "Error!: " . $e->getMessage() . "<br/>";
    die();
}

set_include_path(get_include_path() . PATH_SEPARATOR . 'Classes/');

/** PHPExcel_IOFactory */
include 'PHPExcel/IOFactory.php';
$inputFileName = 'original.xls';
try {
    $objPHPExcel = PHPExcel_IOFactory::load($inputFileName);
} catch(Exception $e) {
    die('Error loading file "'.pathinfo($inputFileName,PATHINFO_BASENAME).'": '.$e->getMessage());
}

/*
    cap [X] - loc [AK]
    targa [D]
    fabbrica [F]
    provincia di residenza [V] - loc [AI]
    comune di residenza [W] - loc [AJ]
    data prima immatricolazione [AB]
    dati anagrafici [T] - loc [AG]
*/

$xls = $objPHPExcel->getActiveSheet()->toArray(null,true,true,true);
$headers = $xls[1];
$loops = 0;
$rows = array_slice($xls, 1);

foreach ( $rows as $row ) {

    $excelData = array(
        'targa'                 => $row['D'],
        'fabbrica'              => $row['F'],
        'immatricolazione'      => $row['AB'],
        'cap'                   => $row['AK'] == '' ? $row['X'] : $row['AK'],
        'datiAnagrafici'        => $row['AG'] == '' ? $row['T'] : $row['AG'],
        'comuneResidenza'       => $row['AJ'] == '' ? $row['W'] : $row['AJ'],
        'provinciaResidenza'    => $row['AI'] == '' ? $row['V'] : $row['AI']
    );

    $insert = $dbh->prepare("
        INSERT INTO 
            data(targa, fabbrica, immatricolazione, cap, datiAnagrafici, comuneResidenza, provinciaResidenza)
            VALUES(:targa, :fabbrica, :immatricolazione, :cap, :datiAnagrafici, :comuneResidenza, :provinciaResidenza)
    ");
    $insert->execute($excelData);
    if ( $insert->rowCount() != 1 ) {
        echo 'Script interrupted at loop nr. '.$loops;
        break;
    }

    ++$loops;

}

$time_end = microtime(true);

$execution_time = ($time_end - $time_start);

echo '<b>Total Execution Time:</b> '.$execution_time.' s';

有什么方法可以优化代码性能吗?循环有问题吗?

谢谢。

【问题讨论】:

  • 您可以先在循环外进行准备 - 查询的结构没有任何变化,是吗?
  • export excel to csv, LOAD DATA INFILE, 速度很快
  • 查看自动提交并为此关闭它
  • @kingkero 太棒了。我将准备移到循环之外,执行时间下降到 8.40 秒
  • 10,000 * 30+ 列 = 高 RAM 使用率。我对 PHPexcel 不是特别熟悉,但您可能想找到一种方法来逐行迭代它,而不是将整个文件加载到内存中。如果您的输入文件不断增长,您最终会耗尽可用内存。

标签: php mysql performance execution-time


【解决方案1】:

如果你可以轻松convert the XLS to CSV,你可以在mysql中使用LOAD DATA INFILE语句。这样会快很多

【讨论】:

  • 感谢您的建议,我一定会这样做的。只是一个简单的问题,我不是 excel 的忠实粉丝,但可以在没有 php 的情况下直接从 excel 本身完成转换?
  • @C.Ovidiu 是的。在 Excel 中,使用菜单 File -&gt; Save as... 并选择 csv 格式。此外,您甚至不需要 PHP 来运行 load data infile 语句。这也可以从您的 shell/workbench/phpmyadmin 中完成。
  • 好的,我去看看CVS和LOAD DATA INFILE,问题是excel文件有20列,但我只需要存储其中的7列,据我了解,CVS被保存了因为它们在数据库中,这意味着文件中的所有列也必须是数据库中的列。我错了吗?
  • LOAD DATA INFILE 允许您指定实际要加载的列
  • @C.Ovidiu LOAD DATA INFILE 'file.txt' INTO TABLE t1 (column1, @ignore, column2, @ignore, column3);`
【解决方案2】:

您可以在单个查询中发送多个值语句。我建议您以这种方式批量插入。

INSERT INTO table (...) VALUES (...), (...), (...);

您可以将每一行的值收集到一个数组中,然后在收集到一定数量(例如 1000)后“刷新”这些值。

【讨论】:

    猜你喜欢
    • 2011-12-20
    • 2014-11-24
    • 1970-01-01
    • 1970-01-01
    • 2016-12-10
    • 1970-01-01
    • 2020-12-01
    • 2013-08-02
    • 1970-01-01
    相关资源
    最近更新 更多