ThinkPHP 面试精讲:Excel 导入导出与大数据量优化

在 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);
}

这是面试中最基本的回答,但仅停留在这个层面远远不够。

二、面试进阶:大数据量场景的核心痛点

当数据量达到数万甚至数十万行时,上述方案会暴露出严重问题:

  1. 内存溢出:PhpSpreadsheet 将整个文件加载到内存中,10 万行数据可能占用数百 MB 甚至超过 1GB 内存。
  2. 超时中断:PHP 默认 max_execution_time 为 30 秒,大数据量处理必然超时。
  3. 数据库压力:逐条 insert 或一次性 insertAll 都会对数据库造成巨大压力。
  4. 用户体验差:浏览器长时间等待无响应,用户不知道进度。

面试官期待你能识别这些痛点,并给出系统性的解决方案。

三、导出优化策略

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 导入导出与大数据量优化

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏