laravel实现大数据csv导出

首先说明几点:

  1. excel格式的文件最大支持100万的数据,所以不考虑使用excel格式
  2. laravel的toArray()方法有内存泄露,所以大量数据导出不能使用.
  3. 当然要使用chunk方法查询数据,然后写到文件中

关于toArray()方法内存泄露排查

chunk()方法的代码块如下(chunk中按理是不用unset的,会自动释放):

 $ormQuery->chunk(1000, function ($data) use (&$firstWrite, $fp) {
    Log::info("开始:".memory_get_usage());
    $data = $data->toArray();
    Log::info("结束:".memory_get_usage());
    unset($data);
    Log::info("usnet 结束:".memory_get_usage());
});

日志

从下往上看

Screen Shot 2017-04-01 at 4.16.38 PM.png

通过日志我们可以分析得出:
在将一个“组块”的 Eloquent 模型转为数组的时候$data = $data->toArray();内存增加了很多.最后unset的时候又没有释放回初始值.实际上chunk中是不需要unset释放的.

解决

使用
DB::table("xxx")->->orderBy(xx)->chunk(xx);方式查询数据,查询结果在使用$data = json_decode(json_encode($data), true);转换成数组,每次chunk执行进去,内存都是一样的,内部也不需要unset.

大数据导出最后做的方案

因为数据量大,所以我的做法是,小量数据导出就直接发送给用户.大数据导出,走队列任务,然后做一个报表管理,用户可以查看进度和完成后下载.

在浏览器中直接返回文件和后台写入文件

函数基本使用

$fp = fopen("文件路径/文件名", "a");  //a表示追加模式
fwrite($fp, chr(0xEF).chr(0xBB).chr(0xBF)); // 添加 BOM,不然excel打开csv是乱码
在chunk的闭包中写数据: fputcsv($fp, $array);
fclose($fp);

在队列中导出

 $fp = fopen(storage_path('app/public/exports')."/".$report->name, "a");
            fwrite($fp, chr(0xEF).chr(0xBB).chr(0xBF)); // 添加 BOM
            $firstWrite = true;
            $query = $expoter->customQuery($query);
            $query->orderBy($tableName.".id")->chunk(1000, function ($data) use (&$firstWrite, $fp, $expoter) {
                $data = json_decode(json_encode($data), true);

                $data = $expoter->customData($data);
                //有一些列总是不导出,如icon,image,images
                $data = ExportUtils::removeInvalids($data);
                //写列名
                if ($firstWrite) {
                    $columnNames = [];
                    //获取列名
                    foreach ($data[0] as $key => $value) {
                        $columnNames[] = admin_translate($key, "coupon");
                    }
                    fputcsv($fp, $columnNames);
                    $firstWrite = false;
                }
                foreach ($data as $item) {
                    fputcsv($fp, $item);
                }
            });

            fclose($fp);

在浏览器中返回数据

 $response = new StreamedResponse(null, 200, [
                'Content-Type'        => 'text/csv',
                'Content-Disposition' => 'attachment; filename="'.$fileName.'"',
            ]);
            $response->setCallback(function () use ($query, $tableName) {
                $out = fopen('php://output', 'w');
                fwrite($out, chr(0xEF).chr(0xBB).chr(0xBF)); // 添加 BOM

                $firstWrite = true;

                $query = $this->customQuery($query);

                $query->orderBy($tableName.".id")->chunk(500, function ($data) use (&$firstWrite, $out) {

                    $data = json_decode(json_encode($data), true);

                    $data = $this->customData($data);
                    //有一些列总是不导出,如icon,image,images
                    $data = ExportUtils::removeInvalids($data);
                    //写列名
                    if ($firstWrite) {
                        $columnNames = [];
                        //获取列名
                        foreach ($data[0] as $key => $value) {
                            $columnNames[] = admin_translate($key, "coupon");
                        }
                        fputcsv($out, $columnNames);
                        $firstWrite = false;
                    }
                    foreach ($data as $item) {
                        fputcsv($out, $item);
                    }
                });

                fclose($out);
            });
            $response->send();


 

posted @ 2019-08-23 14:26  汀风说后端  阅读(545)  评论(0编辑  收藏  举报