在PHP中迭代MASSIVE CSV文件的最佳实践

时间:2009-05-11 01:06:48

标签: php csv

好的,我会尽量保持这个简短,甜蜜和重点。

我们通过将MASSIVE CSV文件上传到基于PHP的CMS,对我们的系统进行大量的GeoIP更新。这件事通常有超过100k的IP地址信息记录。现在,简单地导入这些数据根本不是问题,但我们必须对当前的区域IP地址映射运行检查。

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

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

例如,规则可能如下:

如果'countryName'=='澳大利亚'然后发送到'澳大利亚IP池'

可能需要运行多个规则,并且每个IP记录必须全部应用它们。例如,根据10条规则检查的100k记录将是100万次迭代;不好玩。

我们发现100条记录的2条规则最多需要10分钟才能处理完毕。我完全了解这里的瓶颈,即成功导入必须发生的剪切迭代量;只是没有充分意识到我们可能需要加快一些其他选择。

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

所以,问题是,考虑到我刚刚写的内容,最好的方法是什么才能加快这个过程?不幸的是,升级服务器的硬件JUST对于这个工具不是一个选择,但它们是非常高端的盒子。

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

7 个答案:

答案 0 :(得分:12)

对数据库执行BULK IMPORT(SQL Server是我使用的)。 BULK IMPORT确实需要几秒钟,并且100,000条记录是花生数据库来处理业务规则。我经常在一张超过400万行的桌子上执行类似的数据运算,并且不需要你列出的10分钟。

编辑:我应该指出,是的,我不建议使用PHP。你正在处理原始数据,使用数据库..:P

答案 1 :(得分:1)

这个简单的关键是尽可能多地保留内循环。

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

例如,如果您有内存,并且它对应用程序很实用,请将所有“输出”推迟到主处理之后。如果可行的话,缓存任何输入数据。这最适用于摘要数据或临时数据。

理想情况下,除了读取CSV文件外,在主要处理过程中尽量少做I / O.

PHP是否提供对Unix mmap工具的任何访问权限,这通常是读取文件的最快方式,特别是大文件。

另一个考虑因素是批量插入。例如,可以直接将INSERT语句构建为简单字符串,并以10,50或100行为单位将它们发送到服务器。大多数数据库对SQL语句的大小有一些硬性限制(如64K或其他东西),因此您需要牢记这一点。这将大大减少您到DB的往返。

如果您通过简单的增量创建主键,请执行大量(块1000,10000,无论如何)。这是你可以从内循环中删除的另一件事。

并且,当然,您应该为每一行立即处理所有规则,而不是为每个规则运行记录。

答案 2 :(得分:1)

100k记录不是一个大数字。单个线程的处理时间不是10分钟。无论你是使用PHP还是C,原始工作量都需要大约10分钟。如果你想要它更快,你需要一个比while循环更复杂的解决方案。

以下是我将如何处理它:

  1. 使用map / reduce解决方案并行运行该过程。 Hadoop可能有点矫枉过正。猪拉丁语可以做这个工作。你真的只想要map / reduce问题的地图部分。 IE:您正在分配要由子进程处理的文件块。你的减速器可能是cat。一个简单的版本可能是为每个10K记录块提供PHP fork进程,等待子进程,然后重新组合它们的输出。
  2. 使用队列/网格处理模型。排队文件块,然后让一组机器签入,抓取作业并在某处发送数据。这与map / reduce模型非常相似,只是使用不同的技术,而且你可以通过向网格中添加更多的机器进行扩展。
  3. 如果您可以将您的逻辑编写为SQL,请在数据库中执行。我会避免这种情况,因为大多数Web程序员无法在此级别上使用SQL。此外,SQL在执行RBL检查或ARIN查找等方面受到限制。

答案 3 :(得分:0)

您可以尝试的一件事是在命令行PHP下运行CSV导入。它通常可以提供更快的结果。

答案 4 :(得分:0)

如果您使用PHP来完成这项工作,请将解析切换到Python,因为在这方面它比PHP更快,这个交换应该将该过程加速75%甚至更多。

如果您使用MySQL,您也可以使用LOAD DATA INFILE运算符,我不确定您是否需要在将数据插入数据库之前检查数据。

答案 5 :(得分:0)

现在已经集中精力解决了这个问题。并且,是的,更好的解决方案是在任何时候只读取文件的一部分,解析它,进行验证,进行过滤,然后导出它,然后读取文件的下一部分。我同意这可能不是php的解决方案,虽然你可以在php中做到这一点。只要您具有搜索功能,就可以从文件中的特定位置开始阅读。你是对的它确实增加了更高级别的复杂性,但值得付出额外的努力。 你的数据是纯粹的,即正确分隔,字符串限定,没有破损线等,然后通过一切意味着批量上传到sql数据库。否则,您想知道错误发生的位置,时间和原因,并且能够处理它们。

答案 6 :(得分:0)

我正在使用类似的东西。

我正在使用的csv文件包含葡萄牙语数据(dd / mm / yyyy),我必须将其转换为mysql yyyy-mm-dd。葡萄牙货币:R $ 1.000,15,必须转换成mysql十进制1000,15。修剪可能的空格,最后添加.s    在插入之前有25个变量要处理。

   如果我检查每个$ notafiscal值(选择进入表以查看是否存在并更新),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);