本文目录导读:

- 方案一:使用 Lazy Collection(推荐,Laravel 8+)
- 方案二:配合 Laravel Excel 的 Chunk Query(大数据专用)
- 方案三:手写分块查询 + 写入 CSV/Excel
- 性能与内存对比
- 额外优化技巧
对于 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 |
✅ 极低 | 快 | 百万级+ | 推荐包方案 |
额外优化技巧
- 只选需要的列:
User::query()->select('id', 'name', 'email')->cursor();,避免 SELECT *。 - 增加 PHP 内存限制(不治本):
ini_set('memory_limit', '512M');,但分块方案不需要。 - 使用队列:对于超大数据(> 10 万行),建议使用 Laravel 队列异步导出,用户稍后下载。
Excel::queue(new ChunkedUserExport(), 'users.xlsx')->chain([ // 导出完成后通知用户 ]); - 避免 N+1:
cursor()配合with()使用时要小心,建议用lazy()或chunkById()。
- 不要用
get()或all()导出大数据。 - 推荐使用
cursor()(Laravel 8+ 默认支持)或lazy()。 - 若使用 Laravel Excel,务必实现
WithChunkReading。 - 数据量极大(> 50 万行)时,改用 CSV 格式(比 xlsx 快 10 倍以上)或使用队列。