Php 阅读>;1GB Gzip从外部FTP服务器下载的CSV文件

Php 阅读>;1GB Gzip从外部FTP服务器下载的CSV文件,php,laravel,csv,fgetcsv,Php,Laravel,Csv,Fgetcsv,在我的Laravel应用程序的预定任务中,我正在外部FTP服务器上读取几个大的Gzip CSV文件,从80mb到4gb不等,其中包含我根据产品属性存储在数据库中的产品 我循环浏览要导入的产品提要列表,但每次都返回一个致命错误:“允许内存大小为536870912字节,已耗尽”。我可以将fgetcsv函数的长度参数从1000增加到100000,这解决了较小文件(records[]=$record; 如果(计数($this->records)>=$this->maxRecords){ Eloquent

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

我循环浏览要导入的产品提要列表,但每次都返回一个致命错误:“允许内存大小为536870912字节,已耗尽”。我可以将
fgetcsv
函数的长度参数从
1000
增加到
100000
,这解决了较小文件(<500mb)的问题,但对于较大文件,它将返回致命错误

有没有一种解决方案可以让我下载或解压.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
}

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

因为您正在单个阵列(内存)中加载所有产品

你可以把这行注释掉,看看它是否达到了你的记忆极限

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

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

比如说

类ProductBatchInserter
{
私人$maxRecords=1000;
私人$records=[];
函数addProduct($record){
$this->records[]=$record;
如果(计数($this->records)>=$this->maxRecords){
EloquentModel::insert($this->records);
$this->records=[];
}
}
}
然而,我通常不会将其作为单个类来实现,但在我的项目中,我曾经将它们集成为一个可插入的特性,可以在任何有说服力的模型上使用

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

或者,更容易,但速度要慢得多,只需插入现在分配给数组的行即可。 但是这会给你的数据库增加一个荒谬的负载,而且速度会非常慢

如果GZIP流是瓶颈

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

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

但我希望您正在使用的streamhandler已经在以同样的方式为您执行此操作

如果不是,我的意思是:

$input=gzopen('input.csv.gz','r');
while(($row=fgetcsv($input))!==false){
//做一些记忆安全的事情,就像上面建议的那样
}

如果您需要下载它,有很多方法,但请确保使用内存安全的工具,如fopen/fgets或guzzle stream,不要尝试使用类似file_get_contents()的工具将其加载到内存中

该长度参数与单行的最大长度相关,但不是关于文件的总大小。那个文件中有没有超过那个么长的行?我用一些示例修改了我的初始答案…在对我的生产站点进行了几次更改后,我成功地将所有内容正确导入,再次感谢!出于好奇,
gzopen
streamhandler是否总是比
fgetcsv
更好、更安全的选项?
$importedProducts[] = $product;