excel数据导出sql优化分2篇,本文有sql优化篇
假设有A,B,C,D,E5张表,需要导出这5张表的部分信息,查询条件只涉及到A,B,C的相关字段,约定A,B,C,D,E对应关系都为1对1.
A表字段:ID,TYPEA,NAMEA….;
B表字段:ID,AID,TYPEB,NAMEB…;(AID为A表主键)
C表字段: ID,AID,BID,TYPEC,NAMEC…;(AID为A表主键,BID为B表主键)
D表字段:ID,AID,BID,TYPED,NAMED…;(AID为A表主键,BID为B表主键)
E表字段:ID,AID,BID,TYPEE,NAMEE…;(AID为A表主键,BID为B表主键)
如何导出?
1 简单的实现步骤如下:
1>left join A.B.C,获得ABC的相关信息.
<select id="getABC" ......>
select * from A left join B on A.ID = B.AID
left join C on C.BID = B.AID and C.AID = B.AID
<where>
<if test="TYPEA != NULL and TYPEA!=''">
and A.TYPEA = #{TYPEA}
</if>
<if test="TYPEB != NULL and TYPEB!='' ">
and B.TYPEB = #{TYPEB}
</if>
<if test="NAMEC!= NULL and NAMEC !='' ">
and C.NAMEC CONCAT('%','${NAMEC}','%' )
</if>
</where>
</select>
2>循环获得DE数据,然后在组装excel信息
/** 伪代码 **/
public void getABCDEList(ABC abc) {
List<ABCDE> resultList = new ArrayList<>();
List<ABC> listABC = ABCMapper.getABCList(ABC abc);
for(ABC abc:listABC){
D d = DMappger.getDByID(abc.AID, abc.BID);
E e = EMappger.getDByID(abc.AID, abc.BID);
ABCDE abcde = new ABCDE();
//TODO 根据abc,d,e组装abcde数据
resultList.add(abcde);
}
//TODO excel一次性写入skip,resultList
}
初看起来,也没什么问题,但是当数据量快速增涨的时候,发现excel导出的时间会比较慢,而且当数据条数过W的时候,甚至会出现导出空页的情况。
其实这种简单的写法有3个问题:
1>一次性导出数据可能会OOM,换成分批写入excel
2>left join效率低下,尤其是2张表以上,select * 的写法应该具体到对应的字段
3>for循环获取数据会循环调用sql,执行时间长
针对上述3个问题我们做进一步的优化,称为优化版本:
1> 针对sql条件我们做优化,拆分为2个查询方法,并且加上分页,去掉*用法,left join去掉
方法一: getAB
<select id="getAB" ......>
select A.ID as AID,A.TYPEA,A.NAMEA,
B.ID as BID, B.TYPEB,B.NAMEB from
A , B
<where>
AND A.ID = B.AID
<if test="TYPEA != NULL and TYPEA!=''">
and A.TYPEA = #{TYPEA}
</if>
<if test="TYPEB != NULL and TYPEB!='' ">
and B.TYPEB = #{TYPEB}
</if>
</where>
order by A.ID desc
limit #{skip},#{limit}
</select>
方法二:getABC
<select id="getABC" ......>
select A.ID as AID,A.TYPEA,A.NAMEA,
B.ID as BID, B.TYPEB,B.NAMEB ,
C.ID as CID, C.TYPEC,C.NAMEC ,
from A,B,C
from A left join B on
left join C on
<where>
and A.ID = B.AID
and C.BID = B.AID
and C.AID = B.AID
<if test="TYPEA != NULL and TYPEA!=''">
and A.TYPEA = #{TYPEA}
</if>
<if test="TYPEB != NULL and TYPEB!='' ">
and B.TYPEB = #{TYPEB}
</if>
<if test="NAMEC!= NULL and NAMEC !='' ">
and C.NAMEC CONCAT('%','${NAMEC}','%' )
</if>
</where>
order by A.ID desc
limit #{skip},#{limit}
</select>
2>循环自分页获取数据
/** 伪代码 **/
public void getABCDEList(ABC abc) {
List<ABCDE> resultList = new ArrayList<>();
List<ABC> listABC = new ArrayList<>();
//是否需要获取C信息
boolean needC =true;
//对比测试50,100,200,300,400,500,1000条,发现200效果更佳执行
//总时间最短
int skip = 0;
int limit = 200;
int pageNum = 1;
boolean hasNext = true;
while(hasNext) {
//NAMEC存在
skip = (pageNum - 1) * limit;
//根据查询条件调用对应的方法
if(null != abc.NAMEC && "".equls(abc.NAMEC)) {
listABC = ABCMapper.getABCList(abc,skip,limit);
needC = false;
} else {
listABC = ABCMapper.getABList(abc, skip,limit);
}
//调用批量获取list方法
resultList = getABCDEList(listABC);
//TODO excel分批次写入文件
if(listABC.size() < limit)
hasNext = false;
} else {
//分页自动加1
pageNum += 1;
}
return resultList;
}
方法二:根据listABC获得List<ABCDE>
private List<ABCDE> getABCDEList(List<ABC> listABC){
//循环获取A.B 对应ID信息
List<Integer> AIDS = new ArrayList<>();
List<Integer> BIDS = new ArrayList<>();
for(ABC abc:listABC) {
AIDS.add(abc.AID);
BIDS.add(abc.BID);
}
// 批量获取C.D.E数据
List<C> listC = new ArrayList<>();
List<D> listD = new ArrayList<>();
List<E> listE = new ArrayList<>();
//按照AID+BID放置到对应的map结构里
Map<String,C> mapC = new HashMap<>();
Map<String,D> mapD = new HashMap<>();
Map<String,E> mapE = new HashMap<>();
//数据获取和组装
if(needC){
listC = CMapper.getListCByIDS(AIDS,BIDS);
for(C c:listC) {
mapC.put(String.valueOf(c.AID) + "-" + String.valueOf(c.AID),c);
}
}
listD = DMapper.getListCByIDS(AIDS,BIDS);
listE = EMapper.getListCByIDS(AIDS,BIDS);
for(D d:listD) {
mapD.put(String.valueOf(d.AID) + "-" + String.valueOf(d.AID),d);
}
for(E e:listE) {
mapE.put(String.valueOf(e.AID) + "-" + String.valueOf(e.AID),e);
}
//TODO 根据CDE 对应ID
for(ABC abc:listABC){
ABCDE abcde = new ABCDE();
//TODO 根据abc,(mapC),mapD,mapE装abcde数据
resultList.add(abcde);
}
return resultList;
}
后记:关于excel写入分批写入的优化,会开专门的篇幅概述,敬请期待!