Laravel导出大数据用分块吗

wen PHP项目 7

本文目录导读:

Laravel导出大数据用分块吗

  1. 方案一:使用 Lazy Collection(推荐,Laravel 8+)
  2. 方案二:配合 Laravel Excel 的 Chunk Query(大数据专用)
  3. 方案三:手写分块查询 + 写入 CSV/Excel
  4. 性能与内存对比
  5. 额外优化技巧

对于 Laravel 导出大数据,强烈建议使用分块查询(Chunk),否则极易导致内存溢出或超时。

直接使用 Model::all()DB::table('table')->get() 会将所有数据一次性加载到内存中,当数据量达到数十万或百万级时,PHP 进程会直接耗尽内存崩溃。

以下是针对 Laravel 大数据导出的最佳实践方案,按推荐程度排序:

使用 Lazy Collection(推荐,Laravel 8+)

这是目前最优雅、内存最友好的方式。cursor()lazy() 使用生成器,一次只从数据库游标中读取一条记录。

<?php
namespace App\Exports;
use App\Models\User;
use Maatwebsite\Excel\Facades\Excel;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class UserExport
{
    public function export(): BinaryFileResponse
    {
        return Excel::download(
            new class implements \Maatwebsite\Excel\Concerns\FromCollection, 
                \Maatwebsite\Excel\Concerns\WithHeadings,
                \Maatwebsite\Excel\Concerns\WithMapping
            {
                public function collection()
                {
                    // 关键:使用 cursor() 或 lazy(),而不是 get()
                    return User::query()->cursor(); 
                }
                public function headings(): array
                {
                    return ['ID', 'Name', 'Email'];
                }
                public function map($user): array
                {
                    return [
                        $user->id,
                        $user->name,
                        $user->email,
                    ];
                }
            },
            'users.xlsx'
        );
    }
}

为什么有效?

  • cursor() 使用 PHP 生成器(yield),内存占用几乎固定(与数据总量无关)。
  • 单个查询,不产生额外的磁盘 I/O。

配合 Laravel Excel 的 Chunk Query(大数据专用)

如果你用的是 Laravel Excel(maatwebsite/laravel-excel),官方提供了专门处理大数据的方法,会自动分块写入文件。

<?php
namespace App\Exports;
use App\Models\User;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithChunkReading;
class ChunkedUserExport implements FromQuery, WithMapping, WithHeadings, WithChunkReading
{
    public function query()
    {
        // 返回一个 Query Builder,不用拷贝数据
        return User::query(); 
    }
    public function headings(): array
    {
        return ['ID', 'Name', 'Email'];
    }
    public function map($user): array
    {
        return [
            $user->id,
            $user->name,
            $user->email,
        ];
    }
    // 关键:指定每次从数据库读取多少条
    public function chunkSize(): int
    {
        return 1000; // 建议 500-2000,根据内存大小和列数量调整
    }
}

在 Controller 中调用:

Excel::store(new ChunkedUserExport(), 'users_big.xlsx');

手写分块查询 + 写入 CSV/Excel

如果不想用包,或者需要写入 CSV,可以手动实现分块:

use App\Models\User;
use Illuminate\Support\LazyCollection;
LazyCollection::make(function () {
    User::query()->chunk(1000, function ($users) {
        foreach ($users as $user) {
            yield $user;
        }
    });
})
->map(function ($user) {
    return [$user->id, $user->name, $user->email];
})
// 逐行写入 CSV,避免一次性内存堆积
->chunk(1000)
->each(function ($chunk) {
    // 写入文件(追加模式)
    $file = fopen(storage_path('app/export.csv'), 'a');
    foreach ($chunk as $row) {
        fputcsv($file, $row);
    }
    fclose($file);
});

性能与内存对比

方法 内存消耗 执行速度 适用数据量 建议
get() / all() ❌ 极高 < 1 万行 初学者错误
cursor() / lazy() ✅ 极低 较快 百万级 首选
chunk() + CSV ✅ 低 中等 百万级 无扩展时
Laravel Excel WithChunkReading ✅ 极低 百万级+ 推荐包方案

额外优化技巧

  1. 只选需要的列User::query()->select('id', 'name', 'email')->cursor();,避免 SELECT *。
  2. 增加 PHP 内存限制(不治本):ini_set('memory_limit', '512M');,但分块方案不需要。
  3. 使用队列:对于超大数据(> 10 万行),建议使用 Laravel 队列异步导出,用户稍后下载。
    Excel::queue(new ChunkedUserExport(), 'users.xlsx')->chain([
        // 导出完成后通知用户
    ]);
  4. 避免 N+1cursor() 配合 with() 使用时要小心,建议用 lazy()chunkById()
  • 不要get()all() 导出大数据。
  • 推荐使用 cursor()(Laravel 8+ 默认支持)或 lazy()
  • 若使用 Laravel Excel,务必实现 WithChunkReading
  • 数据量极大(> 50 万行)时,改用 CSV 格式(比 xlsx 快 10 倍以上)或使用队列。

抱歉,评论功能暂时关闭!