导出实现方式小结

十万级导出实现方式小结

①表视图数据直接导

过程

  • 判断导出上限
  • 读取数据
  • 处理数据
  • 写入excel

思路

  • 分批读取数据
  • 分批追加excel数据
  • 最后流式文件导出

具体说明

  • 分批读取数据的时候,注意sql字段加索引,注意纯sql的执行时间,找到合适的每次取limit的数量值区间,避免高频访问数据库又能高效取到更多数据;如果海量数据查询,sql一定要带时间
  •  join表的数量不要超过3个,join多效率低,可采用临时表 or 业务层面等方式避开
  • 如果在导出顺序要求不严格的业务背景下,分批读取依旧很慢,数据未在网关期望时间内读完,可开多线程异步处理
  • 在这里我们采用流式导出,内存占用更小,支持海量导出
  • 获取到分批数据后,进行处理的时候,可开多线程处理

可能遇到的问题

  • 超时:首先定位超时,读取数据 or 处理数据 or 写入文件
    • 一般是在读取数据时候超时,数据没读取完连接就断掉了。开多线程读取 or 处理数据,同时提高网关的时间
    • 最后写入文件那步,采用流式上传,且分批
    • 如果在写入的那步很慢,可尝试拆分多个excel流式下载看看是否会快
  • OOM错误:尝试检查哪步内存过多,再检查服务器的内存大小,过小的话适当提高

顺序严格,伪代码如下

int count = "总数获取";
//分批取数据
for (int i = 0; i < count / 10001; i++) {
   //调用列表方法,更改入参pageIndex和pageSize
   List<Dto> list = getPageList(i + 1,10000).getList(); 
   //追加此批数据,不考虑顺序可使用异步方法asyncAppend提高效率
   excelBuilder.append(list);
}
//todo:生成文件

多线程读取,速度优先,伪代码如下:

int count = "总数获取";
ThreadPoolExecutor executor = new ThreadPoolExecutor(XXXXX);
   executor.submit(()->{
         //分批取数据
       for (int i = 0; i < count / 10001; i++) {
           //调用列表方法,更改入参pageIndex和pageSize
           List<Dto> list = getPageList(i + 1,10000).getList();
           //异步
           excelBuilder.asyncAppend(list);
           //也可调用方法
           //excelBuilder.asyncAppend(()->aa(a,b));
       }
       executor.shutdown();
       });
       while (true) {
          if (executor.isTerminated()) {
               //todo:生成文件
            }
}

②分组数据导

过程

  • 判断导出上限

  • 读取数据

  • 处理数据

  • 写入excel

思路

  • 上限的分组在sql
  • 分批读取所有数据
  • 内存中分组
  • 分批追加excel数据
  • 最后流式文件导出

具体说明

  • 【分页列表接口】 和 【导出中全量数据的分批获取接口】 是两套要分开,不能共用。分页列表为了提高效率,实现方式是在sql中group by并 limit,获取数据速度很快;而导出中用到的批量访问数据,在sql中加入group by后相当慢,加了limit更慢。所以导出读取数据的实现不加分组,是分批全部读取完后,内存中分组

  • 这里要注意,如果实现方式是:

  • A. 分批获取数据,每批数据处理后,excel分批追加数据后,最后写入的方案。

这个方案要注意卡页问题,即多批获取数据后的分组key重复问题,还需在取分页数据前,定义一个map集合P,每次在取完当前批数据后,内存中group by后放入P,放入前检查重复key,重复的话需要把当前批数据addAll之前的map value中。此方案如果注重顺序,必须在group by全部处理好后写入,此时P的size等于导出最大上限,易OOM

  • B. 分批获取数据,每批数据获取后,统一追加到一个集合K,然后多线程异步处理数据并追加excel数据。

也可能会在集合K中OOM,但此K和方案A中P数量级不同,A方案风险低

建议用方案A

第一步判断的count和读取数据时候循环的for中count不是一个count。for时候需要单独查一下,以下写法减少一次访问数据库

int i=0;
while(true){
  i++;
  date = "分批select获取的数据 i+1,10000";
  if(date.size<10000){
     break;
  }
}

小结

也可利用stocket方式导出,但是这种方式易心跳包假死,但问题应该不大

针对大文件造成的内存不够且网络不稳的情况,也可采用分片断点续传方式处理【多线程文件分片】,极大地缩减时间,节约资源。如果访问数据库时间实在缩减到最小了依旧超时且有顺序要求,可以用这种方式实现

也可适当提高处理的线程数

无论是OOM还是超时,总而言之我理解的都是在空间和时间中找个平衡点,针对不同服务器性能选最合适的方案

如果服务器内存允许,sql本身执行也快,我们可以调整下,比如一次性读的数据多一些,以空间换时间,提高用户体验感

如果使用场景是用户接受下载很长时间,他根本不着急,而服务器资源又有限,那我们也可以调整方案节约资源,避免OOM,

在合理内存大小内,选择合适的sql批次方式,以时间换空间,避免服务器崩掉

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值