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
相关产品推荐
相关产品推荐

