在 ThinkPHP 中导入 Excel 时,经常出现 PHP 进程内存耗尽、脚本直接崩溃的情况,这并非代码逻辑错误,而是 PhpSpreadsheet 在加载 xlsx 文件时暗中执行了四项高消耗操作:解压缩、DOM 解析、样式重建以及公式预计算。一个 10 万行且带有格式的表格,轻轻松松就能吃掉 512MB 内存。要彻底解决这个难题,必须从加载策略到数据清洗进行全套优化,下面直接给出干货方案。

第一步:改用真实路径 + 流式加载
上传文件后,第一时间调用 $file->getRealPath() 获取临时文件的真实路径。切勿使用 $file->getPathname() —— 该方法返回的是客户端原始路径(例如 C:Users张三数据.xlsx),服务端根本无法读取。接着使用 fopen($file->getRealPath(), 'r') 包装一层,再传给 IOFactory::load(),这样能绕过 PHP 8+ 对流处理的严格校验,避免出现“Could not open for reading! File does not exist”这类令人头疼的错误。
这里有一个关键点:必须在 upload 完成后的第一行就调用 getRealPath()。ThinkPHP 可能会在后续流程中自动清理 /tmp 下的临时文件,如果延迟调用,文件可能已经不复存在。
第二步:禁用样式与限定工作表
调用 IOFactory::load() 之后,立即执行 $reader->setReadDataOnly(true)。这一步可以跳过字体、边框、颜色等全部样式解析,从而大幅节省内存。如果 Excel 中只有一张有效数据表,再添加一句 $reader->setLoadSheetsOnly(['Sheet1']),防止自动加载隐藏的 sheet 或图表页——这些页面即使为空,也会触发全量解析。
这里有一个容易踩的坑:不要以为设置了 setReadDataOnly(true) 就万事大吉。这个方法必须在 load() 之后、getActiveSheet() 之前调用,顺序错一步,样式照样会挤进内存。
第三步:逐行迭代,手动释放对象
第一步:获取活动工作表:$worksheet = $reader->getActiveSheet();
第二步:启用行迭代器,从第 2 行开始(跳过表头),每 500 行为一块:$rowIterator = $worksheet->getRowIterator(2, 500);
第三步:遍历每行时,开启单元格迭代并跳过空白:$cellIterator = $row->getCellIterator(); $cellIterator->setIterateOnlyExistingCells(true);
第四步:处理完单行后,立即 unset($row); unset($cell); —— PHP 8.3 对 PhpOfficePhpSpreadsheetWorksheetRow 类的释放存在严重滞后,若不手动清理,内存会持续累积。
第五步:每处理完 1000 行,强制回收垃圾:gc_collect_cycles();,防止引用计数残留拖垮垃圾回收机制。
第四步:日期与数字字段精准转换
方法一:判断是否为数字型单元格:$cell->getDataType() === PhpOfficePhpSpreadsheetCellDataType::TYPE_NUMERIC,再结合样式格式码识别日期。
方法二:对疑似日期字段,先取格式码:$formatCode = $cell->getStyle()->getNumberFormat()->getFormatCode();。如果匹配 'yyyy-mm-dd' 或 'yyyy/m/d',再用 PhpOfficePhpSpreadsheetSharedDate::excelToDateTimeObject($cell->getValue()) 转为 DateTime 对象。
方法三:数值列不能只靠 is_numeric() 判断。Excel 中“123456789012”会被自动转成 1.23456789012E+11,必须用 $cell->getFormattedValue() 取显示值,否则入库就是科学计数乱码。
注意:空单元格可能返回 null、'' 或 0。清洗前先做 empty(array_filter($values, 'trim')) 过滤含空格的“假空行”。
第五步:启用 APCu 缓存(可选但强烈推荐)
执行 composer require cache/simple-cache-bridge cache/apcu-adapter 安装依赖。
在 IOFactory::load() 之前插入缓存配置:
$pool = new CacheAdapterApcuApcuCachePool();
$simpleCache = new CacheBridgeSimpleCacheSimpleCacheBridge($pool);
PhpOfficePhpSpreadsheetSettings::setCache($simpleCache);
APCu 缓存能将单元格对象存到共享内存,实测内存占用能下降 80% 以上,而且部署零配置,堪称性价比最高的优化手段。
