在 PHP 面试中,ThinkPHP 框架相关的 Excel 导入导出与大数据量优化是高频考点。面试官不仅考察你对框架本身的熟悉程度,更关注你面对真实业务场景时的架构思维和性能优化能力。本文将从基础实现出发,逐步深入到大数据量场景下的优化策略,帮助你在面试中从容应对。
一、基础方案:PhpSpreadsheet 的集成
在 ThinkPHP 中,最常用的 Excel 处理库是 PhpSpreadsheet(PHPExcel 的继任者)。通过 Composer 安装后,可以在控制器或服务层中直接使用。
导出示例:
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
public function export()
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', '订单号')
->setCellValue('B1', '客户名称')
->setCellValue('C1', '金额');
$list = Db::name('order')->limit(1000)->select();
$row = 2;
foreach ($list as $item) {
$sheet->setCellValue('A' . $row, $item['order_no']);
$sheet->setCellValue('B' . $row, $item['customer']);
$sheet->setCellValue('C' . $row, $item['amount']);
$row++;
}
$writer = new Xlsx($spreadsheet);
$filename = '订单导出_' . date('YmdHis') . '.xlsx';
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="' . $filename . '"');
$writer->save('php://output');
exit;
}
导入示例:
use PhpOffice\PhpSpreadsheet\IOFactory;
public function import()
{
$file = request()->file('excel');
$info = $file->move(runtime_path() . 'import');
$reader = IOFactory::createReader('Xlsx');
$spreadsheet = $reader->load($info->getPathname());
$sheetData = $spreadsheet->getActiveSheet()->toArray();
unset($sheetData[0]); // 去掉表头
$insertData = [];
foreach ($sheetData as $row) {
$insertData[] = [
'order_no' => $row[0],
'customer' => $row[1],
'amount' => $row[2],
];
}
Db::name('order')->insertAll($insertData);
}
这是面试中最基本的回答,但仅停留在这个层面远远不够。
二、面试进阶:大数据量场景的核心痛点
当数据量达到数万甚至数十万行时,上述方案会暴露出严重问题:
- 内存溢出:PhpSpreadsheet 将整个文件加载到内存中,10 万行数据可能占用数百 MB 甚至超过 1GB 内存。
- 超时中断:PHP 默认
max_execution_time为 30 秒,大数据量处理必然超时。 - 数据库压力:逐条
insert或一次性insertAll都会对数据库造成巨大压力。 - 用户体验差:浏览器长时间等待无响应,用户不知道进度。
面试官期待你能识别这些痛点,并给出系统性的解决方案。
三、导出优化策略
1. 使用流式写入器
PhpSpreadsheet 提供了 Xlsx 写入器,但它仍然是内存密集型的。更好的选择是使用 StreamWriter 或直接采用 CSV 格式:
use PhpOffice\PhpSpreadsheet\Writer\Csv;
$writer = new Csv($spreadsheet);
$writer->setUseBOM(true); // 防止中文乱码
$writer->save('php://output');
CSV 格式天然支持流式输出,内存占用极低,且 Excel 可以直接打开。如果业务方强制要求 xlsx 格式,可以考虑使用 spout 库(现更名为 OpenSpout),它专为大文件设计,支持流式读写。
2. 分批查询 + 游标
避免一次性查出所有数据,使用 chunk 或游标逐批处理:
Db::name('order')->chunk(1000, function ($list) use ($sheet, &$row) {
foreach ($list as $item) {
$sheet->setCellValue('A' . $row, $item['order_no']);
// ...
$row++;
}
});
3. 异步导出 + 队列
对于超大数据量,最佳实践是异步导出:
- 用户提交导出请求后,立即返回“任务已提交”
- 通过 ThinkPHP 的队列(如 think-queue)将导出任务推入队列
- 后台 Worker 处理完成后,将文件上传到云存储(OSS/COS)
- 用户通过消息通知或轮询获取下载链接
// 控制器中
Queue::push('app\job\ExportOrder', ['where' => $where, 'user_id' => $userId]);
return json(['code' => 0, 'msg' => '导出任务已提交,请稍后查看']);
四、导入优化策略
1. 流式读取
使用 OpenSpout 的 Reader 逐行读取,避免一次性加载:
use OpenSpout\Reader\XLSX\Reader;
$reader = new Reader();
$reader->open($filePath);
foreach ($reader->getSheetIterator() as $sheet) {
foreach ($sheet->getRowIterator() as $row) {
$cells = $row->getCells();
// 逐行处理,内存占用恒定
}
}
$reader->close();
2. 批量插入 + 事务
将数据积累到一定数量后批量插入,而非逐条插入:
$batch = [];
foreach ($rows as $row) {
$batch[] = [...];
if (count($batch) >= 500) {
Db::name('order')->insertAll($batch);
$batch = [];
}
}
if (!empty($batch)) {
Db::name('order')->insertAll($batch);
}
配合事务使用,保证数据一致性。注意 insertAll 的单次数据量不宜过大,500~1000 条为宜,否则会触及 max_allowed_packet 限制。
3. 数据校验与去重
导入前必须做数据校验:字段格式、必填项、唯一性约束。可以利用 Redis 做去重判断,避免重复导入。
五、面试高频追问与应答思路
追问 1:内存溢出怎么排查?
答:使用 memory_get_usage() 监控内存变化,定位内存增长点。常见原因是将整个文件加载到内存、循环中未释放变量、查询结果集过大。解决方案是流式处理 + 分批查询 + 及时 unset。
追问 2:队列导出如何通知用户?
答:可以通过 WebSocket 推送、站内信、邮件通知,或者前端轮询任务状态接口。ThinkPHP 中可以结合 Redis 存储任务状态。
追问 3:CSV 和 XLSX 如何选择?
答:CSV 适合纯数据导出,内存占用低、生成速度快,但无法设置样式、公式、多 Sheet。XLSX 适合需要格式化的场景,但内存开销大。大数据量优先 CSV,必要时用 OpenSpout 生成 XLSX。
追问 4:导入时如何保证不重复?
答:数据库层面加唯一索引,业务层面用 Redis 做前置去重,导入时用 insertOrIgnore 或 ON DUPLICATE KEY UPDATE。
六、总结
在 ThinkPHP 面试中回答 Excel 导入导出问题,建议遵循“基础实现 → 痛点分析 → 优化方案 → 架构设计”的递进思路。核心要点记住三条:流式处理降内存、分批操作降压力、异步队列提体验。掌握这些,你就能在面试中展现出超越初级开发者的架构视野。
未经允许不得转载:任鹏个人博客 » ThinkPHP 面试精讲:Excel 导入导出与大数据量优化

