【问题标题】:Enhance csv file database import增强 csv 文件数据库导入
【发布时间】:2014-11-05 14:34:23
【问题描述】:

我正在使用下面的脚本将一个大的 csv 文件导入我的数据库。

如果表为空,则在本地计算机上完成该过程大约需要 5 分钟。

如果我使用该文件更新同一张表上的现有值,则需要 15 分钟以上才能完成。

我的 csv 文件包含大约 35,000 行。

我怎样才能加快这个过程?

    if ( $request->get( $_POST["action"] ) == "import" ) {

        $file = $upload->file_upload( "import", "media/import" );
        if ( file_exists( DIR_UPLOAD_PHOTO . "/media/import/" . $file ) ) {

            $file   = DIR_UPLOAD_PHOTO . "/media/import/" . $file;
            $handle = fopen( $file, "r" );

            if ( $handle ) {
                $lines = explode( "\r", fread( $handle, filesize( $file ) ) );
            }

            $total_array = count( $array );

            $x = 0;

            foreach ( $lines as $line ) {

                if ( $x >= 1 ) {
                    $data = explode( "|", $line );

                    $titlu          = trim( addslashes( $data[0] ) );
                    $alias          = $this->generate_seo_link( $titlu );
                    $gramaj         = trim( $data[1] );
                    $greutate       = trim( $data[2] );
                    $pret_total     = trim( $data[3] );
                    $pret_redus     = trim( $data[4] );
                    $poza           = trim( $data[5] );
                    $pret_unitar    = trim( $data[6] );
                    $categorie      = trim( $data[7] );
                    $brand          = trim( addslashes( $data[8] ) );
                    $descriere      = trim( addslashes( $data[9] ) );
                    $vizibil        = trim( $data[10] );
                    $cod            = trim( $data[11] );
                    $nou            = trim( $data[12] );
                    $cant_variabila = trim( $data[13] );
                    $congelat       = trim( $data[14] );
                    $tva            = trim( $data[15] );
                    $stoc           = trim( $data[16] );

                    if ( $cod != "" && $cod != " " ) {

                        $verificare = $database->select( "SELECT alias FROM produse WHERE alias LIKE '%" . $alias . "%'" );
                        for ( $i = 0; $i < $database->countRows(); $i++ ) {
                            if ( $alias == $verificare['alias'][$i] ) {
                                $alias = $this->increment_string( $alias, '_', 1 );
                            } else {
                                $alias = $alias;
                            }
                        }

                        $database->insert( sprintf( "insert into produse set
                            titlu='%s',
                            alias='%s',
                            gramaj='%s',
                            greutate='%s',
                            prettotal='%s',
                            pretredus='%s',
                            poza='%s',
                            pretunitar='%s',
                            categorie='%d',
                            brand='%s',
                            descriere='%s',
                            vizibil='%d',
                            cod='%s',
                            nou='%d',
                            cant_variabila='%d',
                            congelat = '%d',
                            tva = '%s',
                            stoc = '%d'

                            on duplicate key update

                            titlu='%s',
                            gramaj='%s',
                            greutate='%s',
                            prettotal='%s',
                            pretredus='%s',
                            poza='%s',
                            pretunitar='%s',
                            categorie='%d',
                            brand='%s',
                            descriere='%s',
                            vizibil='%d',
                            cod='%s',
                            nou='%d',
                            cant_variabila='%d',
                            congelat = '%d',
                            tva='%s',
                            stoc= '%d'",

                            $titlu, $alias,
                            $gramaj, $greutate, $pret_total, $pret_redus, $poza, $pret_unitar, $categorie,
                            $brand, $descriere, $vizibil, $cod, $nou, $cant_variabila, $congelat,
                            $tva, $stoc,

                            $titlu, $gramaj, $greutate,
                            $pret_total, $pret_redus, $poza, $pret_unitar, $categorie, $brand, $descriere,
                            $vizibil, $cod, $nou, $cant_variabila, $congelat, $tva, $stoc ) );

  }
  }
  $x++;
    }

   }
  }

这是我的递增函数

  function increment_string($str, $separator = '-', $first = 1){
    preg_match('/(.+)'.$separator.'([0-9]+)$/', $str, $match);

    return isset($match[2]) ? $match[1].$separator.($match[2] + 1) : $str.$separator.$first;

  }

【问题讨论】:

  • codereview 可能比 stackoverflow 更多
  • 这可能是由于每次插入都必须检查和更新索引。您可以使用EXPLAIN 来查看在插入期间使用了哪些索引(如果有)。
  • @JayBlanchard - 为什么要在插入时使用索引?为什么人们会立即认为每个数据库减速都可以通过索引来解决。这里的问题是可用的 I/O 操作有限。机械驱动器每秒可以压缩 300 个 I/O。这意味着每秒 300 个查询。如果这些查询是对 MULTIPLE 查询进行分组的事务,则可以有效地使用 HDD 带宽。基本上,强制磁盘在单个 I/O 中刷新 500 个查询会加快速度。这与索引无关
  • 根据我的经验@N.B.我们已经在像这样的 INSERT 操作的大型数据库中看到了这个问题。 UNIQUES 和 INDEXES 对 INSERTS 有影响,许多文章都支持,如 The number of indexes on a table is the most dominant factor for insert performance
  • 慢速插入的主要因素是没有有效地利用磁盘 I/O 和带宽。索引是使用 I/O 的一个子集,但它不是主要因素。这些类型的查询通过将几个插入查询包装到一个事务中进行优化,然后将它们刷新到磁盘而不是逐个执行。

标签: php mysql csv pdo


【解决方案1】:

首先,你做的越少 - 它就越快。但是,由于硬盘驱动器,许多数据库导入速度很慢。不是因为 CPU,也不是因为 RAM 不足 - 是硬盘驱动器。

原因如下:硬盘以每秒输入输出操作数的方式运行 - 我将其称为 I/O。这是制造商不做广告的数字。他们宣传诸如带宽和突发读取之类的东西,这些大多是无用的数字——比如鼠标的 DPI。

机械磁盘的可用 I/O 数量相对较少。该数字因驱动器而异,可以是 100 到 400 个 I/O 之间的任何值。 SSD 的可用 I/O 数量要多得多,从 5000 到 80k(甚至更多)。

这意味着机械磁盘可以在 1 秒内执行 400 次写入,而 SSD 可以执行 5000 次。 问题在于,数据库查询的数据量通常很小(大约 4KB)。

如果您进行简单的数学运算 - 400 I/O * 4KB - 您会得到大约 1.6 MB/秒的数字。这表明您花费了所有 I/O,但占用了磁盘带宽的所有容量。

这也暗示您可以在每个 I/O 上发出更大的数据写入。 用普通话来说,它只是意味着你应该开始一个事务,发出几个 INSERT 查询(比如 50 个 INSERT),然后提交事务。

这样,您为 50 次插入花费了 1 个 I/O。反过来,它实际上快了 50 倍。如果您要使用准备好的语句,这将变得更加有效,因为 MySQL 不必在每次发送查询时都对查询进行 lex。

我不会发送任何代码,因为您应该能够自行修复它。此外,您的代码对 SQL 注入是开放的。你有一些东西要修改,如果你不确定准备好的语句是什么 - 大声说出来。

【讨论】:

  • 我正在为安装 wamp 的主 Windows 分区使用 SSD。关于sql注入我该怎么办?
  • 您说您已经在使用 PDO。你只需要使用准备好的语句,有相当多的资源。编写它实际上比您已经拥有的查询更简单,并且在字符串清理方面它会为您处理所有事情。另外,我会重新考虑在每次插入时增加别名或任何它的策略,您正在扫描整个数据集。这肯定会导致插入过程变慢。
  • 用别名递增函数更新了我的问题
  • "SELECT alias FROM produse WHERE alias LIKE '%" . $alias . "%'" - 那个是坏的。您将遍历每一行,因为这是一个带有 2 个通配符的 LIKE 搜索。如果我是你,我会以完全不同的方式解决问题。我不会像你那样增加别名。我只是按原样保存它,没有任何数字。然后我会创建另一个列,我将存储您要附加到它的那个数字。现在,您可以做的是索引该列并避免 LIKE 搜索。如果存在这样的别名,您可以创建一个触发器来增加新列中的计数器。
【解决方案2】:

将 SELECT 推送到 INSERT 中,以便它们都在服务器上运行,而不是从客户端返回和第四次返回服务器。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-12-30
    • 1970-01-01
    • 2017-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多