PHP导入大Excel班级通信录内存溢出的直接原因是fgetcsv或PhpSpreadsheet默认全量加载数据到内存;应启用setReadDataOnly(true)、流式RowIterator逐行处理,或改用Spout库实现恒定低内存占用。

PHP导入大Excel班级通信录时内存溢出的直接原因
memory_limit 被突破不是偶然,而是 fgetcsv、PHPExcel 或 PhpSpreadsheet 默认加载整张表到内存所致。尤其当班级通信录含 5000+ 行、多列(姓名、电话、家长微信、地址、入学时间等),用 load() 或 getActiveSheet()->toArray() 会一次性把全部单元格转成 PHP 数组——每个字符串在 Zend 引擎里至少占 48 字节以上,1 万行 × 10 列 × 60 字节 ≈ 6MB 只是基础,实际常飙到 200MB+。
常见错误现象包括:Fatal error: Allowed memory size of XXX bytes exhausted,或导入中途 php-fpm worker 被 kill。
用 PhpSpreadsheet 的 setReadDataOnly(true) + setLoadSheetsOnly() 降载
只读数据、跳过样式/公式/宏,能砍掉 60%~80% 内存占用:
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx');
-
$reader->setReadDataOnly(true); —— 忽略字体、边框、条件格式
-
$reader->setLoadSheetsOnly(['Sheet1']); —— 避免多 Sheet 全部加载
- 再用
$spreadsheet = $reader->load($filePath);,此时内存压力明显下降
注意:若通信录含日期列,setReadDataOnly(true) 会让日期变成 Excel 序列号(如 44562),需手动调用 \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject() 转换,别依赖自动类型推断。
真正治本:流式逐行读取(RowIterator)
不把整张表 load 进内存,而是边读边处理:
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx');
$reader->setReadDataOnly(true);
$spreadsheet = $reader->load($filePath);
$worksheet = $spreadsheet->getActiveSheet();
foreach ($worksheet->getRowIterator() as $row) {
$cellIterator = $row->getCellIterator();
$cellIterator->setIterateOnlyExistingCells(false); // 确保空列也读到
$rowData = [];
foreach ($cellIterator as $cell) {
$rowData[] = $cell->getValue();
}
// ✅ 此处立即校验、入库、或写入临时文件,不累积数组
processStudentRecord($rowData);
}
关键点:
- 每次循环只持有一个
$row 和若干 $cell 对象,内存恒定在 ~200KB 内
- 避免使用
$worksheet->toArray() 或 rangeToArray()
- 若需跳过表头,用
if ($row->getRowIndex() === 1) continue;
超大文件(>10MB)建议改用 spout 替代 PhpSpreadsheet
spout 是纯流式库,无 DOM、无样式、无公式,专为大数据导入设计,内存稳定在 3–5MB:
use Box\Spout\Reader\Common\Creator\ReaderEntityFactory;
$reader = ReaderEntityFactory::createReaderFromFile($filePath);
$reader->open($filePath);
foreach ($reader->getSheetIterator() as $sheet) {
foreach ($sheet->getRowIterator() as $row) {
$rowData = $row->getCells();
processStudentRecord($rowData); // 同上
}
}
$reader->close();
兼容性注意:
- 仅支持
.xlsx、.ods、.csv,不支持 .xls(Excel 97-2003)
- 不解析日期/数字格式,所有值都是字符串,需自行
strtotime() 或 (int) 转换
- 无法读取合并单元格内容(班级通信录一般不用合并,影响小)
真正容易被忽略的是:即使用了流式读取,如果在循环里不断 array_push($allRecords, $rowData) 积累全部数据,内存照样爆。必须“读一行、验一行、存一行”,中间不留痕。
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx');$reader->setReadDataOnly(true); —— 忽略字体、边框、条件格式$reader->setLoadSheetsOnly(['Sheet1']); —— 避免多 Sheet 全部加载$spreadsheet = $reader->load($filePath);,此时内存压力明显下降RowIterator)
不把整张表 load 进内存,而是边读边处理:
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx');
$reader->setReadDataOnly(true);
$spreadsheet = $reader->load($filePath);
$worksheet = $spreadsheet->getActiveSheet();
foreach ($worksheet->getRowIterator() as $row) {
$cellIterator = $row->getCellIterator();
$cellIterator->setIterateOnlyExistingCells(false); // 确保空列也读到
$rowData = [];
foreach ($cellIterator as $cell) {
$rowData[] = $cell->getValue();
}
// ✅ 此处立即校验、入库、或写入临时文件,不累积数组
processStudentRecord($rowData);
}
关键点:
- 每次循环只持有一个
$row和若干$cell对象,内存恒定在 ~200KB 内 - 避免使用
$worksheet->toArray()或rangeToArray() - 若需跳过表头,用
if ($row->getRowIndex() === 1) continue;
超大文件(>10MB)建议改用 spout 替代 PhpSpreadsheet
spout 是纯流式库,无 DOM、无样式、无公式,专为大数据导入设计,内存稳定在 3–5MB:
use Box\Spout\Reader\Common\Creator\ReaderEntityFactory;
$reader = ReaderEntityFactory::createReaderFromFile($filePath);
$reader->open($filePath);
foreach ($reader->getSheetIterator() as $sheet) {
foreach ($sheet->getRowIterator() as $row) {
$rowData = $row->getCells();
processStudentRecord($rowData); // 同上
}
}
$reader->close();
兼容性注意:
- 仅支持
.xlsx、.ods、.csv,不支持 .xls(Excel 97-2003)
- 不解析日期/数字格式,所有值都是字符串,需自行
strtotime() 或 (int) 转换
- 无法读取合并单元格内容(班级通信录一般不用合并,影响小)
真正容易被忽略的是:即使用了流式读取,如果在循环里不断 array_push($allRecords, $rowData) 积累全部数据,内存照样爆。必须“读一行、验一行、存一行”,中间不留痕。
.xlsx、.ods、.csv,不支持 .xls(Excel 97-2003)strtotime() 或 (int) 转换











