使用Maatwebsite Laravel Excel 3.1导出超10k数据遇超时问题
Laravel Excel 3.1 大数据导出超时问题排查与解决
问题描述
- 使用Maatwebsite Laravel Excel 3.1包导出数据库中超过10000条数据到Excel,数据量少于10k时运行正常
- 已在导出类中实现
ShouldQueue、WithCustomChunkSize进行分块处理,但仍出现超时错误 - 当前PHP内存限制为512M
现有导出类代码
namespace App\Exports; use App\Events\Download\DownloadComplete; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Support\Responsable; use Illuminate\Support\Arr; use Maatwebsite\Excel\Concerns\Exportable; use Maatwebsite\Excel\Concerns\FromQuery; use Maatwebsite\Excel\Concerns\ShouldAutoSize; use Maatwebsite\Excel\Concerns\WithCustomChunkSize; use Maatwebsite\Excel\Concerns\WithEvents; use Maatwebsite\Excel\Concerns\WithHeadings; use Maatwebsite\Excel\Concerns\WithMapping; use Maatwebsite\Excel\Concerns\WithTitle; use Maatwebsite\Excel\Events\AfterSheet; use App\Traits\ReportDownloadTrait; use Throwable; use App\PreDownloadData; class ReportExport implements FromQuery, WithHeadings, ShouldAutoSize, Responsable, WithEvents, WithTitle, WithMapping, WithCustomChunkSize, ShouldQueue { use Exportable, ReportDownloadTrait; protected array $headings, $inputs; protected int $download_id; public $fileName = "report_download.xlsx"; public $timeout = 1200; // 5 hour public $tries = 3; public function query() { return DownloadData::whereDownloadId($this->download_id)->whereNotNull('row'); } public function map($data): array { return $data->row; } public function batchSize(): int { return 200; } public function chunkSize(): int { return 200; } public function headings(): array { []; } }
问题排查与优化方案
1. 修正模型引用错误
代码中引入了PreDownloadData模型,但query()方法调用的是不存在的DownloadData,这会直接导致查询异常,先修正:
public function query() { return PreDownloadData::whereDownloadId($this->download_id)->whereNotNull('row'); }
2. 匹配队列Worker超时配置
虽然导出类设置了$timeout = 1200,但Laravel队列Worker默认超时为60秒,会提前终止进程。需要同步调整Worker超时:
- 启动Worker时指定超时:
php artisan queue:work --timeout=1200
- 或者在
config/queue.php对应队列连接的配置中设置timeout为1200。
3. 调小分块处理大小
当前chunkSize和batchSize设为200,对于大数据量来说单次处理压力仍较大,建议调小到100或50:
public function chunkSize(): int { return 100; } public function batchSize(): int { return 100; }
4. 关闭自动列宽计算
ShouldAutoSize会为每列计算最优宽度,大数据量下该操作耗时极高,建议移除该接口实现,手动指定列宽:
// 移除 ShouldAutoSize 接口,修改后的类声明: class ReportExport implements FromQuery, WithHeadings, Responsable, WithEvents, WithTitle, WithMapping, WithCustomChunkSize, ShouldQueue
如果需要固定列宽,在WithEvents中配置:
public function registerEvents(): array { return [ AfterSheet::class => function(AfterSheet $event) { // 设置A到Z列宽度为15 $event->sheet->getDelegate()->getColumnDimension('A:Z')->setWidth(15); }, ]; }
5. 内存与查询优化
- 禁用模型事件:临时关闭模型的事件监听,减少额外开销:
public function query() { PreDownloadData::flushEventListeners(); return PreDownloadData::whereDownloadId($this->download_id)->whereNotNull('row'); }
- 只查询必要字段:如果
row是大字段或JSON,确认是否需要全部导出,避免加载冗余数据。
6. 确认队列驱动为异步
如果使用sync驱动,队列会同步执行,无法实现后台异步处理,必须切换到redis、database等异步驱动。
7. 简化map方法逻辑
确保map方法仅做必要的数据转换,避免在该方法中执行复杂计算或额外查询。
内容的提问来源于stack exchange,提问作者user6085744
相关产品推荐
相关产品推荐

