一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情。
前言
上一章讲解了easyexcel导出excel基本功能,不熟悉的朋友可以查看 juejin.cn/post/708274… 但是在实际的项目中会遇到百万级的的Excel导出,本文将讲解大数据量Excel如何高性能导出。
案例分析
之前的项目有个需求需要导出一个月的订单数据,数据量大约为100万左右,新同事的采用了如下的方法实现,相关数据库操作进行了省略,提取了核心实现逻辑。
public static void exportBigData()
{
System.out.println("start export");
long startTime=System.currentTimeMillis();
//查询数据库获得
List<TUser> userList =getUserPagedList(1000000);
//数据大小
int totalCount=userList.size();
//每个excel导出10万行
int pageSize=100000;
//需要多少excel
int totalPage=totalCount%pageSize==0?totalCount/pageSize:totalCount/pageSize+1;
//开始位置
int startRow=0;
for(int i=1;i<=totalPage;i++)
{
String fileName = "E:\\easyExcel\\excel"+i+".xlsx";
ExcelWriter excelWriter = null;
try
{
excelWriter = EasyExcel.write(fileName, TUser.class).build();
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
//通过内存分页导出数据
int fromIndex=startRow;
int endIndex = startRow+pageSize;
if(endIndex>totalCount)
{
excelWriter.write(userList.subList(fromIndex, totalCount), writeSheet);
}
else
{
excelWriter.write(userList.subList(fromIndex, endIndex), writeSheet);
}
//
startRow = startRow+pageSize;
}
finally
{
if (excelWriter != null)
{
excelWriter.finish();
}
}
}
System.out.println("end export...");
System.out.println("export cost time:"+(System.currentTimeMillis()-startTime));
}
复制代码
系统刚开始运行导出excel没出问题,当系统的并发量提升后,多人同时导出excel则出现了内存溢出的异常。
问题分析
上述代码存在如下问题:
1.该方法是通过查询一次性将所有的数据加载到内存中,如果是多人同时操作,那么会将系统的内存很快会占用完。
2.分页的实现采用的是java的subList来实现java的内存分页,内存分页的性能和效率比较低
3.操作完成后没有将查询对象的内存数据进行释放,这样导致用户多导几次excel,内存会持续的新增。
解决方案
针对大数据的excel导出,我们需要从几个方面来提升性能
1.数据库性能
查询数据的时需要考虑通过条件查询数据,查询条件是否有添加对应的索引。
2.数据库连接池性能,
数据库的连接池是否进行了调优来满足相应的需求
3.java程序代码性能
采用数据库分页的方式来查询数据,分批导出。 excel数据对象导出完成后,需要释放内存。
通过问题分析和讲解后,同事进行了修改后的代码如下:
public static void exportBigData2()
{
System.out.println("start export");
long startTime=System.currentTimeMillis();
//查询数据总数量
int totalCount=getTotalUserCount();
//每个excel导出10万行
int pageSize=100000;
//需要多少excel
int totalPage=totalCount%pageSize==0?totalCount/pageSize:totalCount/pageSize+1;
for(int i=1;i<=totalPage;i++)
{
String fileName = "E:\\easyExcel\\excel"+i+".xlsx";
ExcelWriter excelWriter = null;
try
{
excelWriter = EasyExcel.write(fileName, TUser.class).build();
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
//通过分页多次查询
PageHelper.startPage(i, pageSize);
List<TUser> userList =getUserPagedList(pageSize);
excelWriter.write(userList, writeSheet);
//释放内存
userList.clear();
}
finally
{
if (excelWriter != null)
{
excelWriter.finish();
}
}
}
复制代码
解决方案的1,2两点通过优化数据库来提升性能,后续再重点讲解,第3点采用了数据库分页的方式进行了优化。经过同事的优化,基本上已经可以满足需求。
为什么没有用多对线程来导出excel
这个问题之前有考虑,经过相关测试的发现,easyexcel多线程同时往一个sheet页内写数据件,会导致excel文件损坏。如果有测试成功的,欢迎提供相关代码。
后续优化
- 报表模块与业务模块分离,导出报表不会影响核心模块功能,同时提升报表模块性能。
- 数据库采用一主多从的方式,报表模块读取从库数据,提升性能,虽然从库和主库的数据存在延迟,但是查询报表的数据基本都是隔天数据,所以影响不大。