You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel-Excel大文件导出内存耗尽且耗时递增问题求助

Laravel Maatwebsite\Excel 大文件导出内存耗尽与耗时递增问题

在Laravel中使用Maatwebsite\Excel包以chunk+队列方式导出大文件时,出现每个AppendQueryToSheet任务耗时逐次增加,最终在执行CloseSheet时触发内存耗尽错误(报错位置为PhpSpreadsheet的Cells.php第395行),导致导出提前终止。


控制器代码

$exporter = new ReportExport($this->validatedData, $this->id);
$exporter->queue($file_name,
    'gallery', \Maatwebsite\Excel\Excel::XLSX)->chain([
        new ExportEnrollmentsJobWhenDone($this->id),
    ]);

ReportExport 导出类代码

class ReportExport implements FromQuery, WithHeadings, WithEvents, WithCustomChunkSize, ShouldQueue
{
    use Exportable;

    protected $progress;
    protected $progressIncrement;
    public $totalRows;


    protected $validatedData;

    protected $id;

    public function __construct($validatedData, $id)
    {
        $this->validatedData = $validatedData;
        $this->id = $id;
        $this->progress = 0;
    }


    public function headings(): array
    {
        return [
            // 自定义表头
        ];
    }


    /**
     * @return array
     */
    public function registerEvents(): array
    {
        return [
            // 自定义事件
        ];
    }

    public function query()
    {
        // 自定义查询逻辑
        $this->totalRows = $enrollments->count();
        $this->progressIncrement = 100 / ceil($this->totalRows / $this->chunkSize());
        return $enrollments->orderBy('id', 'desc');
    }

    public function chunkSize(): int
    {
        return 5000;
    }
}

终端日志

2023-07-09 13:06:49 Maatwebsite\Excel\Jobs\QueueExport ............. RUNNING
  2023-07-09 13:06:49 Maatwebsite\Excel\Jobs\QueueExport ....... 117.01ms DONE
  2023-07-09 13:06:50 Maatwebsite\Excel\Jobs\AppendQueryToSheet ...... RUNNING
  2023-07-09 13:06:56 Maatwebsite\Excel\Jobs\AppendQueryToSheet  6,822.89ms DONE
  2023-07-09 13:06:57 Maatwebsite\Excel\Jobs\AppendQueryToSheet ...... RUNNING
  2023-07-09 13:07:08 Maatwebsite\Excel\Jobs\AppendQueryToSheet  10,715.10ms DONE
  2023-07-09 13:07:09 Maatwebsite\Excel\Jobs\AppendQueryToSheet ...... RUNNING
  2023-07-09 13:07:23 Maatwebsite\Excel\Jobs\AppendQueryToSheet  14,725.47ms DONE
  2023-07-09 13:07:24 Maatwebsite\Excel\Jobs\AppendQueryToSheet ...... RUNNING
  2023-07-09 13:07:43 Maatwebsite\Excel\Jobs\AppendQueryToSheet  18,818.70ms DONE
  2023-07-09 13:07:44 Maatwebsite\Excel\Jobs\AppendQueryToSheet ...... RUNNING
  2023-07-09 13:08:07 Maatwebsite\Excel\Jobs\AppendQueryToSheet  22,557.33ms DONE
  2023-07-09 13:08:08 Maatwebsite\Excel\Jobs\AppendQueryToSheet ...... RUNNING
  2023-07-09 13:08:34 Maatwebsite\Excel\Jobs\AppendQueryToSheet  26,214.35ms DONE
  2023-07-09 13:08:35 Maatwebsite\Excel\Jobs\AppendQueryToSheet ...... RUNNING
  2023-07-09 13:09:05 Maatwebsite\Excel\Jobs\AppendQueryToSheet  29,926.11ms DONE
  2023-07-09 13:09:06 Maatwebsite\Excel\Jobs\AppendQueryToSheet ...... RUNNING
  2023-07-09 13:09:40 Maatwebsite\Excel\Jobs\AppendQueryToSheet  33,737.76ms DONE
  2023-07-09 13:09:41 Maatwebsite\Excel\Jobs\AppendQueryToSheet ...... RUNNING
  2023-07-09 13:10:18 Maatwebsite\Excel\Jobs\AppendQueryToSheet  37,834.62ms DONE
  2023-07-09 13:10:20 Maatwebsite\Excel\Jobs\AppendQueryToSheet ...... RUNNING
  2023-07-09 13:11:02 Maatwebsite\Excel\Jobs\AppendQueryToSheet  42,433.48ms DONE
  2023-07-09 13:11:03 Maatwebsite\Excel\Jobs\AppendQueryToSheet ...... RUNNING
  2023-07-09 13:11:49 Maatwebsite\Excel\Jobs\AppendQueryToSheet  45,406.48ms DONE
  2023-07-09 13:11:49 Maatwebsite\Excel\Jobs\AppendQueryToSheet ...... RUNNING
  2023-07-09 13:12:43 Maatwebsite\Excel\Jobs\AppendQueryToSheet  53,667.69ms DONE
  2023-07-09 13:12:44 Maatwebsite\Excel\Jobs\AppendQueryToSheet ...... RUNNING
  2023-07-09 13:13:43 Maatwebsite\Excel\Jobs\AppendQueryToSheet  58,882.30ms DONE
  2023-07-09 13:13:44 Maatwebsite\Excel\Jobs\CloseSheet .............. RUNNING

   Symfony\Component\ErrorHandler\Error\FatalError 

  Allowed memory size of 536870912 bytes exhausted (tried to allocate 83886080 bytes)

  at vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php:395
    391▕         }
    392▕         $column = 0;
    393▕         $row = '';
    394▕         sscanf($cellCoordinate, '%[A-Z]%d', $column, $row);
  ➜ 395▕         $this->index[$cellCoordinate] = (--$row * self::MAX_COLUMN_ID) + Coordinate::columnIndexFromString((string) $column);
    396▕ 
    397▕         $this->currentCoordinate = $cellCoordinate;
    398▕         $this->currentCell = $cell;
    399▕         $this->currentCellIsDirty = true;


   Whoops\Exception\ErrorException 

  Allowed memory size of 536870912 bytes exhausted (tried to allocate 83886080 bytes)

  at vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php:395
    391▕         }
    392▕         $column = 0;
    393▕         $row = '';
    394▕         sscanf($cellCoordinate, '%[A-Z]%d', $column, $row);
  ➜ 395▕         $this->index[$cellCoordinate] = (--$row * self::MAX_COLUMN_ID) + Coordinate::columnIndexFromString((string) $column);
    396▕ 
    397▕         $this->currentCoordinate = $cellCoordinate;
    398▕         $this->currentCell = $cell;
    399▕         $this->currentCellIsDirty = true;

      +1 vendor frames 
  2   [internal]:0
      Whoops\Run::handleShutdown()

解决方案

1. 优化chunk大小与Excel配置

修改config/excel.php,降低chunk尺寸并关闭不必要的功能:

return [
    'exports' => [
        'chunk' => [
            'size' => 1000, // 从5000降到1000,减少单任务单元格处理量
        ],
        'pre_calculate_formulas' => false, // 关闭公式预计算,节省内存
    ],
    'temporary_files' => [
        'local_path' => storage_path('framework/cache'), // 指定临时文件存储路径
    ],
];

小chunk能分散内存占用,避免单元格索引数组过快膨胀,缓解耗时递增问题。

2. 禁用PhpSpreadsheet单元格索引缓存

在导出类的事件中关闭Cells集合的索引缓存,这是内存暴涨的核心原因:

public function registerEvents(): array
{
    return [
        BeforeSheet::class => function(BeforeSheet $event) {
            $event->sheet->getDelegate()->getCellCollection()->setCacheEnabled(false);
        },
    ];
}

Cells.php第395行的错误源于$this->index数组随单元格数量持续膨胀,禁用缓存后直接操作单元格,大幅降低内存占用。

3. 移除query方法中的冗余count查询

当前代码在query()中执行$enrollments->count()会额外触发全表查询,消耗资源,改为单独计算总行数:

public function query()
{
    $enrollments = // 你的查询逻辑
    // 移除$this->totalRows = $enrollments->count();
    return $enrollments->orderBy('id', 'desc');
}

如需进度条,可单独创建队列任务先计算总行数,再启动导出任务。

4. 调高队列进程内存限制

临时调整队列进程的内存配额:

php artisan queue:work --memory=1024

或修改php.ini中的memory_limit = 1024M,此为临时缓解方案,需配合其他优化使用。

5. 改用流式导出(百万级数据推荐)

使用FromIterator+cursor()实现流式导出,逐行加载数据不占内存:

class ReportExport implements FromIterator, WithHeadings, ShouldQueue
{
    use Exportable;

    protected $validatedData;
    protected $id;

    public function __construct($validatedData, $id)
    {
        $this->validatedData = $validatedData;
        $this->id = $id;
    }

    public function headings(): array
    {
        return [/* 自定义表头 */];
    }

    public function iterator(): Iterator
    {
        return \App\Models\Enrollment::query()
            ->where(/* 你的查询条件 */)
            ->orderBy('id', 'desc')
            ->cursor(); // 迭代器逐行加载数据
    }
}

内容的提问来源于stack exchange,提问作者Mohammad Nizar Arbash

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 21:01:59