【问题标题】:Reading > 1GB GZipped CSV files from external FTP server从外部 FTP 服务器读取 > 1GB GZipped CSV 文件
【发布时间】:2020-11-07 08:52:16
【问题描述】:

在我的 Laravel 应用程序的计划任务中,我正在外部 FTP 服务器上读取几个大的 gzip 压缩 CSV 文件,从 80mb 到 4gb 不等,其中包含我根据产品属性存储在我的数据库中的产品。

我循环浏览我想要导入的产品提要列表,但每次都返回致命错误:“允许的内存大小为 536870912 字节已用尽”。我可以将fgetcsv 函数的长度参数从1000 提高到100000,这解决了较小文件(

是否有一种解决方案可以让我下载或解压缩 .csv.gz 文件、读取行(批量或逐个)并将产品插入我的数据库而不会耗尽内存?

$feeds = [
    "feed_baby-mother-child.csv.gz",
    "feed_computer-games.csv.gz",
    "feed_general-books.csv.gz",
    "feed_toys.csv.gz",
];

foreach ($feeds as $feed) {
    $importedProducts = array();
    $importedFeedProducts = 0;

    $csvfile = 'compress.zlib://ftp://' . config('app.ftp_username') . ':' . config('app.ftp_password') . '@' . config('app.ftp_host') . '/' . $feed;

    if (($handle = fopen($csvfile, "r")) !== FALSE) {
        $row = 1;
        $header = fgetcsv($handle, 1, "|");
                
        while (($data = fgetcsv($handle, 1000, "|")) !== FALSE) {
            if($row == 1 || array(null) !== $data){ $row++; continue; }
                    
            $product = array_combine($header, $data);
            $importedProducts[] = $product;
        }

        fclose($handle);
    } else {
        echo 'Failed to open: ' . $feed . PHP_EOL;
        continue;
    }
    
    // start inserting products into the database below here
}

【问题讨论】:

  • 该长度参数与单行的最大长度有关,但与文件的总大小无关。该文件中是否有超过这么大长度的行?
  • 我修改了我最初的答案,有一些示例......

标签: php laravel csv fgetcsv


【解决方案1】:

问题可能不是 gzip 文件本身, 当然你可以下载它,然后处理它,这将保持同样的问题。

因为您将所有产品加载到单个数组(内存)中

$importedProducts[] = $product;

你可以注释掉这一行,看看它是否达到了你的内存限制。

通常我会创建一个像 addProduct($product) 这样的方法来处理它的内存安全。

然后,您可以在进行批量插入之前确定产品的最大数量。为了达到最佳速度..我通常使用 1000 到 5000 行之间的东西。

例如

class ProductBatchInserter
{
    private $maxRecords = 1000;
    private $records = [];
    
    function addProduct($record) {
        $this->records[] = $record;
        if (count($this->records) >= $this->maxRecords) {
           EloquentModel::insert($this->records);
           $this->records = [];
        }
    }
}

不过我通常不会将它实现为单个类,但在我的项目中,我曾经将它们集成为 BulkInsertable 特征,可用于任何 eloquent 模型。

但这应该给你一个方向,你可以如何避免内存限制。

或者,更简单,但速度明显更慢,只需插入您现在将其分配给数组的行。 但这会给您的数据库带来可笑的负载,而且速度会非常慢。

如果 GZIP 流是瓶颈

正如我所料,这不是问题,但如果是这样,那么您可以使用 gzopen()

https://www.php.net/manual/en/function.gzopen.php

并将 gzopen 句柄嵌套为 fgetcsv 的句柄。

但我希望您正在使用的流处理程序已经以相同的方式为您执行此操作..

如果不是,我的意思是这样的:

$input = gzopen('input.csv.gz', 'r'); 


while (($row = fgetcsv($input)) !== false) {
 // do something memory safe, like suggested above
}

如果您无论如何都需要下载它,有很多方法可以做到,但请确保您使用内存安全的东西,例如 fopen / fgets 或 guzzle 流,并且不要尝试使用类似 file_get_contents() 加载的东西进入记忆

【讨论】:

  • 在对我的生产站点进行了几次更改后,我设法让所有内容都正确导入,再次感谢您!只是出于好奇,gzopen 流处理程序总是比fgetcsv 更好、更安全的选择吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-11
  • 2018-10-02
  • 1970-01-01
  • 2017-12-23
  • 1970-01-01
  • 2013-10-03
  • 1970-01-01
相关资源
最近更新 更多