为了账号安全,请及时绑定邮箱和手机立即绑定

从外部 FTP 服务器读取 > 1GB GZipped CSV 文件

从外部 FTP 服务器读取 > 1GB GZipped CSV 文件

PHP
呼啦一阵风 2023-08-11 11:00:24
在我的 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}
查看完整描述

1 回答

?
HUH函数

TA贡献1836条经验 获得超4个赞

问题可能不是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 特征。


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


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


如果 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() 这样的东西将其加载到内存中


查看完整回答
反对 回复 2023-08-11
  • 1 回答
  • 0 关注
  • 104 浏览

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号