代码之家  ›  专栏  ›  技术社区  ›  Wilhelm Murdoch

在PHP中迭代大量csv文件的最佳实践

  •  7
  • Wilhelm Murdoch  · 技术社区  · 15 年前

    好的,我会尽量简短、贴切、切中要害。

    我们通过将大量的csv文件上传到基于php的cms来对系统进行大量的geoip更新。这个东西通常有超过100K的IP地址信息记录。现在,简单地导入这些数据根本不是问题,但是我们必须对当前的区域IP地址映射进行检查。

    这意味着我们必须验证数据,比较和拆分重叠的IP地址等。必须对每一份记录进行检查。

    不仅如此,我刚刚创建了一个字段映射解决方案,允许其他供应商以不同的格式实现其geoip更新。这是通过对csv更新中的ips记录应用规则来完成的。

    例如,规则可能看起来像:

    如果'countryname'='australia',则发送到'australian ip pool'

    可能需要运行多个规则,每个IP记录都必须应用它们。例如,要根据10条规则检查的10万条记录将是100万次迭代,这并不有趣。

    我们发现10万条记录需要10分钟才能处理2条规则。我完全了解这里的瓶颈,这是成功导入必须发生的迭代的剪切量;只是不完全了解任何其他可能需要加快速度的选项。

    有人建议将文件分成块,服务器端。我不认为这是一个可行的解决方案,因为它给已经很复杂的系统增加了另一层复杂性。必须打开、分析和拆分文件。然后脚本也必须遍历这些块。

    所以,问题是,考虑到我刚刚写的,最好的方法是什么来加快这个过程?不幸的是,仅仅为这个工具升级服务器的硬件并不是一个选项,但是首先它们是相当高端的设备。

    不像我想的那么短,但是的。Halps?:(

    7 回复  |  直到 15 年前
        1
  •  12
  •   Some Canuck    15 年前

    对数据库执行批量导入(我使用的是SQL Server)。批量导入需要几秒钟的时间,10万条记录对于数据库来说简直就是小菜一碟。我经常在一个超过400万行的表上执行类似的数据压缩,而这并不需要您列出的10分钟。

    编辑:我应该指出,是的,我不推荐使用PHP。你要处理原始数据,使用数据库。P

        2
  •  1
  •   Will Hartung    15 年前

    实现这一点的简单方法是尽可能多地将工作从内部循环中移出。

    简单地说,你在内部循环中做的任何事情都是“10万次”,所以什么都不做是最好的(但肯定不实际),所以尽可能少做是下一个最好的选择。

    例如,如果您有内存,并且对于应用程序来说很实用,那么可以将任何“输出”推迟到主处理之后。如果可行,缓存任何输入数据。这对汇总数据或偶然数据最有效。

    理想情况下,保存以读取csv文件,在主处理过程中尽可能少地执行I/O操作。

    PHP是否提供对unix mmap工具的任何访问,这通常是读取文件(尤其是大型文件)的最快方式。

    另一个考虑是批量插入。例如,将insert语句构建为简单的字符串,并以10行、50行或100行的数据块形式发送到服务器,这很简单。大多数数据库对SQL语句的大小都有一些硬限制(比如64K之类的),所以您需要记住这一点。这将大大减少您到数据库的往返行程。

    如果要通过简单的增量创建主键,请执行en mass(1000、10000块,随便什么)。这是另一件可以从内部循环中移除的事情。

    当然,对于每一行,您应该一次处理所有的规则,而不是针对每个规则运行记录。

        3
  •  1
  •   Gary Richardson    15 年前

    10万条记录并不多。对于一个线程来说,10分钟不是一个糟糕的工作处理时间。不管您使用的是PHP还是C,一条直线上要完成的原始工作量大概是10分钟左右。如果您希望它更快,您需要比while循环更复杂的解决方案。

    以下是我将如何解决的问题:

    1. 使用map/reduce解决方案并行运行进程。Hadoop可能是杀戮过度。生猪拉丁语可以胜任这项工作。你真的只是想要地图的一部分/减少问题。IE:你把一块文件分出来,由一个子进程来处理。你的减速器可能是 cat . 一个简单的版本可能是为每一个10K记录块创建一个php fork进程,等待子进程,然后重新组装它们的输出。
    2. 使用队列/网格处理模型。将文件块排队,然后让一组机器签入、获取作业并将数据发送到某个地方。这与map/reduce模型非常相似,只是使用了不同的技术,而且您可以通过向网格中添加更多的机器来进行缩放。
    3. 如果您可以将逻辑写为SQL,请在数据库中进行。我会避免这样做,因为大多数Web程序员不能在这个级别上使用SQL。此外,SQL在做诸如RBL检查或ARIN查找之类的事情方面也有一定的局限性。
        4
  •  0
  •   Mark Phipps    15 年前

    您可以尝试在命令行php下运行csv导入。它通常提供更快的结果。

        5
  •  0
  •   Alix Axel    15 年前

    如果您使用PHP来完成这项工作,那么将解析转换为python,因为它在这方面比PHP快得多,所以这种交换应该可以将处理速度提高75%甚至更多。

    如果您使用的是MySQL,那么您也可以使用加载数据内嵌操作符,但我不确定在将数据插入数据库之前是否需要检查数据。

        6
  •  0
  •       15 年前

    已经集中精力解决这个问题一段时间了。而且,是的,更好的解决方案是在任何时候只读取文件的一部分,分析它,进行验证,进行过滤,然后导出它,然后读取文件的下一部分。我同意这可能不是PHP的解决方案,尽管您可以用PHP来解决。只要您有一个seek函数,这样您就可以从文件中的特定位置开始读取。你是对的,它确实增加了更高的复杂性,但这一点额外的努力是值得的。 如果您的数据是纯数据,即正确分隔、字符串限定、没有断行等,那么无论如何,都要批量上传到SQL数据库中。否则,您需要知道错误发生的位置、时间和原因,并能够处理它们。

        7
  •  0
  •   heavyrick    11 年前

    我在做类似的事情。

    我正在处理的csv文件包含葡萄牙语数据(dd/m m/yyyy),我必须将其转换为mysql yy-m m-dd。葡萄牙语货币:r$1.000,15,必须转换为mysql decimal 1000,15。修剪可能的空格,最后添加斜杠。

    插入前有25个变量需要处理。

    如果我检查每个$notafcial值(选择into表查看是否存在并更新),PHP将处理大约60K行。但是如果我不检查它,PHP处理超过100万行。

    服务器使用内存为4GB的脚本本地宿主(内存为2GB),在这两种情况下,它都处理半行。

    mysqli_query($db,"SET AUTOCOMMIT=0");
    mysqli_query($db, "BEGIN");
    mysqli_query($db, "SET FOREIGN_KEY_CHECKS = 0");
    fgets($handle); //ignore the header line of csv file
    
    while (($data = fgetcsv($handle, 100000, ';')) !== FALSE):
     //if $notafiscal lower than 1, ignore the record
     $notafiscal = $data[0];  
     if ($notafiscal < 1):
      continue;
     else:
      $serie = trim($data[1]); 
      $data_emissao = converteDataBR($data[2]);
      $cond_pagamento = trim(addslashes($data[3]));
      //...
      $valor_total = trim(moeda($data[24]));
      //check if the $notafiscal already exist, if so, update, else, insert into table
      $query = "SELECT * FROM venda WHERE notafiscal = ". $notafiscal ;
      $rs = mysqli_query($db, $query);
      if (mysqli_num_rows($rs) > 0):
        //UPDATE TABLE
      else:
        //INSERT INTO TABLE
      endif;
    endwhile;
    
    mysqli_query($db,"COMMIT");
    mysqli_query($db,"SET AUTOCOMMIT=1");
    mysqli_query($db,"SET FOREIGN_KEY_CHECKS = 1");
    mysqli_close($db);