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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 00:48:20