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

Laravel中PhpSpreadsheet ChunkReadFilterService分块读取异常求助

PhpSpreadsheet分块读取Excel累加重复问题解决

问题描述

在Laravel/MySQL环境下使用PhpSpreadsheet库分块读取大Excel文件,期望每次读取300行,但分块功能异常,每次读取都会累加之前的块数据,导致数据库出现大量重复及无效数据。

代码运行输出:

300
600
900
1051

预期输出应为:

300
300
300
151

原代码

$totalRows=1051;
$currentRows=1;
while($currentRows <= $totalRows){
                
    $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
    $reader->setReadDataOnly(true);

    $chunkFilter = new ChunkReadFilterService();
    $reader->setReadFilter($chunkFilter);
    $chunkFilter->setRows($currentRows,300);
  
    $spreadsheet = $reader->load($path);
    
    $sheetData = [];
   
    $sheetData   = $spreadsheet->getActiveSheet()->toArray(null, true, true, true);
    $currentRows += 300;
    
    echo count($sheetData);
    echo '</br>';
}

问题原因

核心问题出在ChunkReadFilterService类的过滤逻辑错误:大概率是readCell方法仅限制了结束行,未限制起始行,导致每次读取时都会包含从第一行到当前结束行的所有数据,而非仅当前块的行。

解决方案

1. 修正ChunkReadFilterService类

确保过滤逻辑仅读取指定起始行到结束行的范围:

namespace App\Services; // 根据实际项目命名空间调整

use PhpOffice\PhpSpreadsheet\Reader\IReadFilter;

class ChunkReadFilterService implements IReadFilter
{
    private int $startRow = 0;
    private int $endRow = 0;

    /**
     * 设置读取的行范围
     * @param int $startRow 起始行号(从1开始)
     * @param int $chunkSize 每次读取的行数
     */
    public function setRows(int $startRow, int $chunkSize): void
    {
        $this->startRow = $startRow;
        $this->endRow = $startRow + $chunkSize - 1;
    }

    public function readCell(string $column, int $row, string $worksheetName = ''): bool
    {
        // 仅读取指定范围内的行
        return $row >= $this->startRow && $row <= $this->endRow;
    }
}

2. 优化读取循环代码

将Reader实例化移到循环外减少开销,并动态计算最后一次的读取行数:

$totalRows = 1051;
$currentRows = 1;
$chunkSize = 300;

// 仅初始化一次Reader,减少重复创建对象的性能损耗
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
$reader->setReadDataOnly(true);

$chunkFilter = new ChunkReadFilterService();
$reader->setReadFilter($chunkFilter);

while ($currentRows <= $totalRows) {
    // 计算当前块的实际行数,避免最后一次超出总行数
    $currentChunkSize = min($chunkSize, $totalRows - $currentRows + 1);
    
    $chunkFilter->setRows($currentRows, $currentChunkSize);
  
    $spreadsheet = $reader->load($path);
    
    $sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true);
    
    // 处理当前块数据(例如写入数据库)
    // processImportData($sheetData);
    
    echo count($sheetData);
    echo '</br>';
    
    $currentRows += $chunkSize;
    
    // 释放内存,避免大文件导致内存溢出
    $spreadsheet->disconnectWorksheets();
    unset($spreadsheet);
}

说明

  • 修正后的过滤逻辑确保每次仅读取当前块的行,不会累加之前的数据。
  • 循环外初始化Reader减少重复创建对象的性能损耗。
  • 动态计算currentChunkSize确保最后一次读取剩余的行数(示例中为151行)。
  • 添加disconnectWorksheets和unset释放内存,避免处理超大文件时内存溢出。

内容的提问来源于stack exchange,提问作者Muhammad Adnan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 09:02:52