PHPExcel导入3万行以上Excel失效 如何正确添加chunk filter分块读取
核心问题
原有代码直接调用PHPExcel_IOFactory::load()加载整个文件,会将全量单元格数据、样式信息全部读入内存,3万行以上的文件即使调高内存限制,也会因为全量解析开销过大导致运行缓慢。之前分块过滤器未生效的核心原因是:在加载完整个文件后才做分块逻辑,此时数据已经全部进入内存,过滤器不会产生任何效果。
正确实现代码
首先在加载逻辑前定义分块过滤器类,再按照「初始化读取器→设置分块规则→逐块读取→释放内存」的流程改造代码:
// 定义分块读取过滤器 class ChunkReadFilter implements PHPExcel_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 = '') { return ($row >= $this->startRow && $row < $this->endRow); } } // 原有基础配置保留 ini_set('upload_max_filesize', '64M'); ini_set('memory_limit', '4096M'); ini_set('max_execution_time', 0); ini_set('max_input_time', -1); $fileName = $_FILES["production"]["tmp_name"]; $chunkSize = 800; // 分块大小建议500-1000行,平衡内存和IO性能 $startRow = 2; // 数据从第2行开始,第1行是表头 // 固定值移到循环外,避免重复计算 $date = date("Y-m-d", strtotime('-1 days')); $skeydate = date("Ymd", strtotime('-1 days')); // 初始化读取器,不要直接调用load $fileType = PHPExcel_IOFactory::identify($fileName); $objReader = PHPExcel_IOFactory::createReader($fileType); $objReader->setReadDataOnly(true); // 只读模式,跳过样式等无用数据,内存占用降70%+ $objReader->setLoadSheetsOnly(0); // 只读取第一个工作表 // 绑定分块过滤器 $chunkFilter = new ChunkReadFilter(); $objReader->setReadFilter($chunkFilter); // 临时读取第一行获取总行数、最大列,避免全量加载 $chunkFilter->setRows(1, 1); $tempExcel = $objReader->load($fileName); $totalRows = $tempExcel->getActiveSheet()->getHighestRow(); $highestColumn = $tempExcel->getActiveSheet()->getHighestColumn(); $tempExcel->disconnectWorksheets(); unset($tempExcel); // 循环分块处理 for ($startRow = 2; $startRow <= $totalRows; $startRow += $chunkSize) { $chunkFilter->setRows($startRow, $chunkSize); $objPHPExcel = $objReader->load($fileName); $sheet = $objPHPExcel->getActiveSheet(); // 处理当前块内的行,最后一块可能不足chunkSize,做边界判断 $blockEndRow = min($startRow + $chunkSize - 1, $totalRows); for ($row = $startRow; $row <= $blockEndRow; $row++) { $rowData = $sheet->rangeToArray('A'.$row.':'.$highestColumn.$row, NULL, TRUE, FALSE); $line = htmlentities($rowData[0][0]); $workedorder = htmlentities($rowData[0][1]); $modelnumber = htmlentities($rowData[0][2]); $revision = htmlentities($rowData[0][3]); $serialnumber = !empty($rowData[0][4]) ? htmlentities($rowData[0][4]) : ''; $lpn = htmlentities($rowData[0][5]); $shift = htmlentities($rowData[0][6]); $datecreated = htmlentities($rowData[0][7]); $datecompleted = htmlentities($rowData[0][8]); $repairflag = htmlentities($rowData[0][9]); $skey = $skeydate.$line; // 执行原有入库逻辑 importproduction( $mysql_connectionLinkLocal, $line, $workedorder, $modelnumber, $revision, $serialnumber, $lpn, $shift, $repairflag, $date, $skey, $datecreated, $datecompleted ); } // 每块处理完必须手动释放内存,避免内存累积溢出 $objPHPExcel->disconnectWorksheets(); unset($objPHPExcel); }
优化注意事项
- 分块大小不要设置超过1000行,过大会导致单块内存占用过高,过小会增加磁盘IO次数拖慢速度,根据服务器配置调整到500-1000区间即可。
- 如果导入文件支持转成CSV格式,直接用PHP原生
fgetcsv逐行读取,性能会比PHPExcel高5-10倍,更适合十万级以上数据导入。 - 入库逻辑如果是单条insert,可以改成批量insert(每块凑成一批入库),能大幅降低数据库IO开销,进一步提升导入速度。
- 不要在循环内做重复运算,比如原代码里的日期变量是固定值,放在循环外即可。
内容的提问来源于stack exchange,提问作者REVATHI
相关产品推荐
相关产品推荐

