十万级导出实现方式小结
①表视图数据直接导
过程
- 判断导出上限
- 读取数据
- 处理数据
- 写入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批次方式,以时间换空间,避免服务器崩掉