【问题标题】:Best practices for iterating over MASSIVE CSV files in PHP在 PHP 中迭代 MASSIVE CSV 文件的最佳实践
【发布时间】:2009-05-11 01:06:47
【问题描述】:

好的,我会尽量保持简短、甜蜜和中肯。

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

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

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

例如,规则可能如下所示:

如果 'countryName' == 'Australia' 然后发送到 'Australian IP Pool'

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

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

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

所以,问题是,考虑到我刚刚写的内容,加快这个过程的最佳方法是什么?不幸的是,只为这个工具升级服务器的硬件不是一个选项,但它们一开始就是相当高端的盒子。

没有我想象的那么短,但是是的。哈普斯? :(

【问题讨论】:

    标签: php csv


    【解决方案1】:

    对数据库执行 BULK IMPORT(我使用的是 SQL Server)。 BULK IMPORT 从字面上看需要几秒钟,而 100,000 条记录对于数据库处理业务规则来说是微不足道的。我会定期对超过 400 万行的表执行类似的数据处理,而不需要您列出的 10 分钟。

    编辑:我应该指出,是的,我不推荐使用 PHP。您正在处理原始数据,请使用 DATABASE.. :P

    【讨论】:

    • 真是太糟糕了——我从来没有得到过“接受的答案”。
    【解决方案2】:

    简单的关键是尽可能避免内部循环的工作。

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

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

    理想情况下,为了读取 CSV 文件,在主处理期间尽可能少地执行 I/O。

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

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

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

    当然,您应该为每一行一次处理所有规则,而不是为每条规则运行记录。

    【讨论】:

      【解决方案3】:

      10 万条记录并不是一个很大的数字。 10 分钟对于单个线程来说并不是一个糟糕的工作处理时间。无论您使用的是 PHP 还是 C,直线完成的原始工作量可能约为 10 分钟。如果您希望它更快,您将需要一个比 while 循环更复杂的解决方案.

      我会这样处理它:

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

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

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

          【讨论】:

            【解决方案6】:

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

            【讨论】:

              【解决方案7】:

              我正在使用类似的东西。

              我正在使用的 csv 文件包含我必须转换为 mysql yyyy-mm-dd 的葡萄牙语数据 (dd/mm/yyyy)。葡萄牙货币:R$ 1.000,15 必须转换为 mysql 十进制 1000,15。修剪可能的空格,最后添加斜线。

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

              如果我检查每个 $notafiscal 值(选择表以查看是否存在和更新),php 处理大约 60k 行。但如果我不检查它,php 处理超过 100 万行。

              服务器使用 4GB 内存 - 脚本 localhosting(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);    
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-02-19
                • 2011-05-09
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2023-03-06
                相关资源
                最近更新 更多