PHP百万数据级导出excel

单次导出一百万条数据,保证服务器的资源不会因为这个导出变卡。导出的时间要求不作限制,大家有没有比较好的解决方案哈。

本帖已被设为精华帖!
本帖由系统于 1年前 自动加精
gongmeng
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 34
fatrbaby

本站有一个大佬 @viest ,写了一个扩展 xlswriter 导出百万不在话下。

2年前 评论
gongmeng (楼主) 2年前

xlswriter 扩展 + 队列,一次性导出的话应该会内存溢出或者超时

2年前 评论
gongmeng (楼主) 2年前
中文 1年前

在 Laravel 中导出大量数据,避免内存溢出或者超时问题可以使用流式输出,将数据一行一行地输出到浏览器或者文件中,而不是将所有数据都加载到内存中。Laravel 中可以使用 Chunk 方法来实现流式输出,Chunk 方法将查询结果分块处理,每次处理一定数量的数据

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Response;
use App\Models\User;

public function exportUsers(Request $request)
{
    $fileName = 'users.csv';

    $headers = [
        'Content-type' => 'text/csv',
        'Content-Disposition' => sprintf('attachment; filename="%s"', $fileName),
    ];

    $totalRows = User::count();
    $chunkSize = 1000;
    $totalChunks = ceil($totalRows / $chunkSize);

    $stream = fopen('php://output', 'w');

    // 写入表头
    fputcsv($stream, ['ID', 'Name', 'Email']);

    for ($i = 1; $i <= $totalChunks; $i++) {
        $offset = ($i - 1) * $chunkSize;
        $users = User::offset($offset)->limit($chunkSize)->get();

        foreach ($users as $user) {
            // 写入一行数据
            fputcsv($stream, [$user->id, $user->name, $user->email]);
        }

        // 强制刷新输出缓冲区
        ob_flush();
        flush();
    }

    fclose($stream);

    return Response::make('', 200, $headers);
}

在这个示例代码中,我们使用 php://output 创建一个流,将数据输出到这个流中。这样可以避免将所有数据读入内存中,降低内存占用。

我们通过循环分块读取数据,每次读取 $chunkSize 条数据,然后将这些数据写入到流中。在写入数据后,我们使用 ob_flush () 和 flush () 强制刷新输出缓冲区,将已经写入的数据发送到客户端,避免输出缓冲区占用过多内存。

最后,我们通过 Response 返回一个空内容的响应,同时设置相应的 Content-Type 和 Content-Disposition 头,告诉浏览器下载这个 CSV 文件。

为了避免超时和内存溢出问题,我们可以在 php.ini 中设置 max_execution_time 和 memory_limit。如果需要在代码中设置这些限制,可以使用 set_time_limit () 和 ini_set () 函数。

2年前 评论
gongmeng (楼主) 2年前
fatrbaby 2年前
pi_phq 2年前
gongmeng

这是我写的一段导出百万数据的测试代码,其整个运行时间 40 秒左右,这个 xslwriter 真是厉害哈

        ini_set('memory_limit', '1024M');
        set_time_limit(0);

        $path = \think\facade\App::getRootPath() . 'public/upload/';

        $config = [
            'path' => $path,
        ];

        $fileName = 'tutorial01.xlsx';

        $excel      = new \Vtiful\Kernel\Excel($config);
        $fileObject = $excel->constMemory($fileName, NULL, false);
        $fileHandle = $fileObject->getHandle();

        $format    = new \Vtiful\Kernel\Format($fileHandle);
        $boldStyle = $format->bold()->toResource();

        $fileObject = $fileObject->setRow('A1', 10, $boldStyle) // 写入数据前设置行样式
                                 ->header(['id', 'person_id', 'person_name', 'gmt_create', 'gmt_modified']);

        for ($i = 5000; $i <= 1000000; $i += 5000) {
            $offset     = $i - 5000;
            $limit      = 5000;
            $data       = Db::table('t1')->limit($offset, $limit)->select();
            $data       = collect($data)->map(function ($item) {
                return collect($item)->values();
            })->toArray();
            $fileObject = $fileObject->data($data);
        }

        $filePath = $excel->output();

        header("Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        header('Content-Disposition: attachment;filename="' . $fileName . '"');
        header('Content-Length: ' . filesize($filePath));
        header('Content-Transfer-Encoding: binary');
        header('Cache-Control: must-revalidate');
        header('Cache-Control: max-age=0');
        header('Pragma: public');
        ob_clean();
        flush();
        if (copy($filePath, 'php://output') === false) {
            exit('copy file to path error');
        }
        @unlink($filePath);
2年前 评论
晏南风 2年前
caixingyue 1年前

如果仅仅是数据,完全可以先导出为 csv。然后本地转换为 excel 即可。

2年前 评论
Complicated 2年前

大数据导出,要防止内存溢出,需要用游标读取(laravel 封装的 cursor 是使用生成器完成的),再用 xlswriter,最近我也根据这个写了大数据导入导出,导出就是前面说的这个逻辑,导入用分片上传,再游标读取每行,在分批存入数据库

2年前 评论
gongmeng (楼主) 2年前
kkokk (作者) 2年前
gongmeng (楼主) 2年前

yield + csv 怎么样?到处多少都没有问题吧?

2年前 评论
gongmeng (楼主) 2年前

写成 csv,用工具转成 excel。

2年前 评论
巅峰互联

异步导出,最后压缩 下载

2年前 评论

我不是杠,我就是想知道这么多的数据,office 可以打开吗?另外,Excel 最大好像是 1048576 行。

2年前 评论
gongmeng (楼主) 2年前
lovewei 1年前

我是这样写的,分批查询,边查询边输出的,截取一段代码:

代码已被折叠,点此展开
2年前 评论

我是这么写的,贴个代码参考一下,当时测试 20 几秒

代码已被折叠,点此展开
2年前 评论

我这样写的

代码已被折叠,点此展开
2年前 评论

导出 excel 内存超了主要就是因为执行期间,整个 excel 都放到内存里,这样子数据越多就越卡

我这边总结了三个方法

  1. 离线异步生成 excel,然后前端只需要直接下载 excel 文件就不会有这个问题
  2. 使用 csv,分批查询,一批一批的输出到前端
  3. 选一个可以分批处理的 excel 扩展,这个扩展是支持分批处理,解决 csv 样式问题
2年前 评论

xlswriter 扩展,然后队列消费。sql 查询导出的话有条件的最好用 chunkByIdchunk 最终是 offset,复杂点的后面会一言难尽~

2年前 评论
sanders

我这里有个问题,我想通过多个队列任务给一个 xlsx 文件添加多个 sheet 的方式来减少内存开销。但我发现下次加载相同的文件会覆盖上一次生成的文件。是否有通过不同的队列任务打开相同的文件,追加 sheet 的方法?

2年前 评论

我用的方案是 队列,因为导出的数据较大(带图片),高清图,一张好几 MB,还得限制 Excel 文件的大小。

1年前 评论