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

PhpSpreadsheet加载大文件后内存无法释放问题求助

解决PhpSpreadsheet读取大Excel时内存泄漏导致Apache崩溃的问题

在Linux Apache环境的Laravel项目中,使用PhpSpreadsheet读取包含5万+行数据的超大Excel时,进程内存占用可达数GB。即便按照官方指引执行$spreadsheet->disconnectWorksheets();和unset($spreadsheet);,内存仍无法释放,最终导致服务器崩溃。以下是当前使用的读取方法:

public static function readFile($file, string $sheet = null): array
{
    $bookarray = array();
    $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
    $reader->setReadDataOnly(true);
    $spreadsheet = $reader->load($file);

    if ($sheet != null) {
        $worksheet = $spreadsheet->getSheetByName($sheet);
        if ($worksheet == null) {
            $worksheet = $spreadsheet->getActiveSheet();
        }
    } else {
        $worksheet = $spreadsheet->getActiveSheet();
    }

    foreach ($worksheet->getRowIterator() as $row) {
        $rowarray = array();
        $cellIterator = $row->getCellIterator();

        foreach ($cellIterator as $cell) {
            array_push($rowarray, $cell->getValue());
        }

        array_push($bookarray, $rowarray);
        unset($rowarray);
        unset($cellIterator);
    }

    $bookarray = array_values($bookarray);
    $spreadsheet->disconnectWorksheets();
    unset($spreadsheet);
    unset($worksheet);
    unset($reader);

    return $bookarray;
}

核心问题分析

当前代码一次性将整个Excel的所有行加载到内存中的$bookarray,即便销毁PhpSpreadsheet相关变量,这个大数组依然占用大量内存,直到请求结束。此外,PHP的垃圾回收机制不会立即释放内存,Apache的进程复用机制会导致内存无法及时回收。

解决方案

1. 分块读取Excel(使用Read Filter)

不要一次性加载整个文件,而是通过自定义Read Filter分批次读取行数据,大幅降低内存峰值:

class ChunkReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter
{
    private $startRow = 0;
    private $endRow = 0;

    public function setRows($startRow, $chunkSize)
    {
        $this->startRow = $startRow;
        $this->endRow = $startRow + $chunkSize;
    }

    public function readCell($column, $row, $worksheetName = '')
    {
        if ($row >= $this->startRow && $row < $this->endRow) {
            return true;
        }
        return false;
    }
}

public static function readLargeFile($file, string $sheet = null, $chunkSize = 1000)
{
    $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
    $reader->setReadDataOnly(true);
    $filter = new ChunkReadFilter();
    $reader->setReadFilter($filter);

    // 获取总行数
    $spreadsheet = $reader->load($file);
    if ($sheet !== null) {
        $worksheet = $spreadsheet->getSheetByName($sheet) ?? $spreadsheet->getActiveSheet();
    } else {
        $worksheet = $spreadsheet->getActiveSheet();
    }
    $totalRows = $worksheet->getHighestRow();
    $spreadsheet->disconnectWorksheets();
    unset($spreadsheet);

    // 分块处理
    for ($startRow = 1; $startRow <= $totalRows; $startRow += $chunkSize) {
        $filter->setRows($startRow, $chunkSize);
        $spreadsheet = $reader->load($file);
        $worksheet = $spreadsheet->getSheetByName($sheet) ?? $spreadsheet->getActiveSheet();
        
        foreach ($worksheet->getRowIterator($startRow, min($startRow + $chunkSize - 1, $totalRows)) as $row) {
            $rowarray = [];
            foreach ($row->getCellIterator() as $cell) {
                $rowarray[] = $cell->getValue();
            }
            // 这里直接处理行数据,比如写入数据库,不要存到大数组里
            // processRow($rowarray);
        }

        $spreadsheet->disconnectWorksheets();
        unset($spreadsheet, $worksheet);
        gc_collect_cycles(); // 强制触发垃圾回收
    }
}

2. 避免返回完整数据集

如果业务不需要一次性获取所有行数据,直接在循环内处理每行(如写入数据库),不要将所有行存储到$bookarray中返回,这是内存占用过高的主要原因之一。

3. 强制触发垃圾回收

在销毁变量后手动调用gc_collect_cycles(),强制PHP回收循环引用的内存(PhpSpreadsheet对象存在大量循环引用)。

4. 调整Apache/PHP运行模式

  • 改用PHP-FPM替代Apache模块运行PHP:每个请求结束后,FPM进程会销毁,内存自动释放,避免Apache进程复用导致的内存累积。
  • 如果坚持用Apache模块,调整MaxRequestWorkers、MaxConnectionsPerChild等参数,限制子进程的请求次数和内存占用,让Apache定期回收进程。

5. 代码细节优化

  • 替换array_push为直接赋值:$rowarray[] = $cell->getValue()比array_push更高效。
  • 减少变量作用域:循环内的变量尽量在循环内定义,避免全局作用域残留。

内容的提问来源于stack exchange,提问作者Tomas De pamphilis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 19:53:12