使用 Lazy Collections 来提高 Laravel Excel 读取的性能(轻松支持百万数据)

Laravel

在 Laravel 6 中添加了一种新类型的集合: Lazy Collections。 如果需要处理非常大的数据集(数千或数百万行)而不会遇到内存限制,那么它们是非常棒的。

我最近的任务是在工作中的一个项目中重构 Excel 导出。 问题是,由于数据集太大,Laravel 无法处理,导出无法再创建。 数据库查询返回了大约300,000个结果! 应用程序产生超时或一直内存不足。

一种天真的方法是增加超时时间或内存限制,并希望下次出现问题时,另一个人会处理这个问题。 但这不是我的工作方式。 我不喜欢创可贴。 我喜欢具体的、长期的解决方案。


Laravel Excel 扩展包已经相当灵活。 通过在使用 FromQuery-concerns 时使用「chunks」,它在减少数据库负载方面做得很好。 然而,我们的导出仍然很难处理大数据集。

我和我的同事讨论过,我们是否应该完全重写这个特性: 将导出推送到队列中,并在导出结束时向用户发送通知。 然而,这个功能在这个应用程序中只是一个很小的东西。 对我们来说,仅仅为了一个简单的导出而增加如此多的开销是没有意义的。

那天晚些时候,我有一个小小的「我发现了」的时刻,因为我记得 Laravel 中有 LazyCollections 这个东东。

我重新编写了导出: 它现在使用 FromCollection-concern,而不是 FromQuery。 我必须对 collection()方法进行的惟一更改是将查询构建器链末尾的 get()方法替换为 cursor()

下面是我们导出功能的简化版本。 Request对象通过构造函数传递,因此我们可以根据用户在 UI 中选择的内容对查询进行调整。

<?php 

namespace App\Exports;

use App\User;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\FromCollection;
use Illuminate\Http\Request;

class UsersExport implements FromCollection
{
    use Exportable;

    protected Request $request;

    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    public function collection()
    {
        return User::query()
            ->when($this->request->get('include_subscribed'), function ($q) {
                return $q->where('is_subscribed', true);
            })
            ->cursor(); // ← 重要的一点
    }
}

我相信你在你的项目中遇到了内存问题。 你增加了内存限制,希望问题已经解决了(我自己已经做过无数次了)。

如果是在 Laravel 项目中,我希望,我可以让你重新查看该代码并使用 LazyCollections 重写。


修复这个问题非常有趣,所以我做了一个小小的基准测试: 我们的导出现在可以轻松地导出数百万行,而不会遇到内存限制。 太酷了! 😎

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://stefanzweifel.io/posts/lazy-coll...

译文地址:https://learnku.com/laravel/t/42018

本帖已被设为精华帖!
本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 18

謝謝提供資訊

4年前 评论

我尝试导出10000条数据:内存不够 file

file

3年前 评论
ishuaijie 3年前

这个cursor()本质上消耗了mysql的内存,也不是特别友好。最好是自己用chunkById一行一行读出来,然后一行一行地写入文件

3年前 评论

可惜要 6.0 :sob:

3年前 评论

cursor() 用的是php生成器

3年前 评论

实操了一下 感觉并不会减少内存的开销,导出时间返回长了,不知道是哪里的姿势不对?

3年前 评论
Complicated

@di-gua 关键是这玩意(excel插件)没法一行行的写啊

2年前 评论

这个并不会减少内存消耗

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!