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

如何在Yii2框架结合MongoDB环境下高效导出2000万条数据至PDF文件?

针对你在Yii2+MongoDB环境下导出2000万条数据到PDF的性能问题,我整理了几个核心优化方向,结合你的代码场景给出具体建议:

一、数据查询层优化(MongoDB + Yii2)

这部分是性能瓶颈的核心源头,慢查询会直接拖垮整个导出流程:

  • 避免一次性加载全量数据:你当前设置pagination => false会把2000万条数据全部加载到内存,不仅速度慢还极易引发内存溢出。建议改成分批查询,每次拉取固定数量的数据(比如1万条)处理:
    // 替换原ActiveDataProvider的全量查询逻辑,改用分页循环
    $totalCount = $query->count();
    $pageSize = 10000; // 可根据服务器内存调整
    $pages = ceil($totalCount / $pageSize);
    
    for ($page = 1; $page <= $pages; $page++) {
        $batchData = $query->offset(($page-1)*$pageSize)->limit($pageSize)->all();
        // 处理当前批次数据,生成对应HTML片段
    }
    
  • 给MongoDB创建复合索引:你的查询涉及isParent、tm_id、callstatus、hangup、start_epoch/end_epoch等过滤条件,创建匹配查询优先级的复合索引能大幅提升查询速度:
    // 在MongoDB Shell中执行索引创建命令
    db.canned_reports.createIndex(
        { isParent: 1, tm_id: 1, callstatus: 1, hangup: 1, start_epoch: -1 }
    )
    
    注意:范围查询字段(如start_epoch)要放在索引的最后位置。
  • 减少ORM开销:如果ActiveRecord的封装带来额外性能损耗,可以直接使用Yii2的MongoDB QueryBuilder或原生驱动查询,跳过ActiveRecord实例化过程:
    $mongoDb = Yii::$app->mongodb->getDatabase();
    $collection = $mongoDb->getCollection('canned_reports');
    // 构建原生查询条件
    $filter = [
        'isParent' => 1,
        'tm_id' => (string)Yii::$app->user->identity->tm_id,
        'callstatus' => ['$ne' => 'completed'],
        'hangup' => 'ORIGINATOR_CANCEL',
        'start_epoch' => ['$gte' => $startEpoch, '$lte' => $endEpoch]
    ];
    // 分批获取数据,减少内存占用
    $cursor = $collection->find($filter)->sort(['start_epoch' => -1])->batchSize(10000);
    foreach ($cursor as $document) {
        // 直接处理原生文档数据
    }
    
二、PDF生成层优化(wkhtmltopdf)

wkhtmltopdf对超大HTML内容的渲染效率极低,核心优化思路是分批生成小PDF,最后合并:

  • 分批生成+合并PDF:不要一次性把所有HTML传给addPage,每处理一批数据就生成一个独立的临时PDF文件,最后用工具合并成最终文件:
    $tempPdfs = [];
    $pageSize = 10000;
    $totalCount = $query->count();
    $pages = ceil($totalCount / $pageSize);
    $tempDir = sys_get_temp_dir();
    
    for ($page = 1; $page <= $pages; $page++) {
        $batchData = $query->offset(($page-1)*$pageSize)->limit($pageSize)->all();
        // 渲染当前批次的表格HTML(用renderPartial避免加载布局)
        $batchHtml = $this->renderPartial('_report_table', ['data' => $batchData]);
    
        $tempPdf = new Pdf([
            'binary' => '/usr/local/bin/wkhtmltopdf',
            'options' => ['disable-smart-shrinking', 'no-outline', 'disable-javascript'],
        ]);
        $tempPdf->addPage($batchHtml);
        $tempPath = $tempDir . '/batch_' . $page . '.pdf';
        $tempPdf->saveAs($tempPath);
        $tempPdfs[] = $tempPath;
    }
    
    // 使用pdftk或Ghostscript合并临时PDF(需提前安装对应工具)
    $finalPdfPath = '/path/to/your/final_report.pdf';
    exec("pdftk " . implode(' ', $tempPdfs) . " cat output " . $finalPdfPath);
    
    // 清理临时文件
    foreach ($tempPdfs as $path) {
        unlink($path);
    }
    
  • 优化HTML渲染效率:
    • 简化表格样式:去掉不必要的CSS、JavaScript,wkhtmltopdf对复杂CSS的渲染耗时极高;
    • 扁平化DOM结构:避免嵌套过深的HTML标签,减少渲染计算量;
    • 提前缓存总数:不要在PDF生成时重复调用$dataProvider->query->count(),提前查询一次并缓存结果。
  • wkhtmltopdf参数调优:添加以下参数减少渲染负担:
    $options = [
        'dpi' => 96,
        'image-quality' => 80, // 降低图片质量(如果报表无图片可设为0或禁用)
        'disable-smart-shrinking',
        'no-outline',
        'disable-javascript', // 禁用JS(无交互需求时建议开启)
        'no-images', // 报表无图片时直接禁用图片加载
        'lowquality', // 可选:降低输出质量换取速度
    ];
    
三、架构层面优化

2000万条数据的导出属于长时任务,同步请求必然会超时,必须改成异步处理:

  • 用Yii2队列异步执行导出:把导出任务放到队列(如Redis Queue、RabbitMQ),由后台Worker进程处理,完成后通知用户下载:
    1. 创建导出任务类:
      class ExportPdfJob extends BaseObject implements \yii\queue\JobInterface
      {
          public $params; // 接收查询参数
      
          public function execute($queue)
          {
              // 这里放入查询+PDF生成的完整逻辑
              // 生成完成后可通过邮件、站内信通知用户,或把文件路径存入数据库供下载
          }
      }
      
    2. 控制器中推送任务到队列:
      Yii::$app->queue->push(new ExportPdfJob(['params' => $params]));
      return $this->asJson(['message' => '导出任务已启动,完成后将通知你']);
      
  • 分块导出(可选):如果用户不需要单一超大PDF,可以按时间范围或其他维度拆分多个小文件,用户可分别下载,同时降低单文件处理压力。
四、其他小技巧
  • 内存监控与释放:在批次循环中定期用unset()释放不再需要的变量,避免内存泄漏;
  • MongoDB连接优化:确保使用合适的batchSize参数,减少数据库与应用的网络往返次数;
  • 临时扩容服务器:wkhtmltopdf是CPU密集型任务,临时提升服务器CPU核心数、内存资源,能显著提升渲染速度。

内容的提问来源于stack exchange,提问作者p1992

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.01 01:02:49