【实战】大批量数据导出的方案

1,247 阅读3分钟

【实战】大批量数据导出的方案

背景

业务需要导出将近6w多的全单位的数据,有些数据在当前表中不存在,需要现计算。这个功能是汇总的功能,而且涉及的表很多(将近40张),表是真TM的多,需要显示的字段也是超多,我是真服了,当时是怎么设计的。

方案

优化架构和表结构是不行的,只能在这40张表的基础上继续搞。想的办法是分开批量查询。主体思想就是先把主要的数据查询出来,再写多个子查询(涉及到计算的),子查询都用 in 来写,一次查询上千条,然后这样再分批次,最后再汇总。

具体实现操作

这是主表,主要的数据,里面会查询出project_id,再去根据project_id 去作为其他子查询的条件。这里 * 省略具体的字段。

 select * from
 serp_em_project_info a
 left join serp_em_construction_plan b
 on a.project_id = b.project_id
 left join serp_em_project_account_info c
 on a.project_id = c.project_id
 left join js_sys_office o
 on a.office_code = o.office_code
 left join serp_base_project_developer d
 on a.project_developer =d.project_developer_id
 left  join  js_sys_area e
 on a.construction_area = e.area_code
 left join js_sys_office uo
 on uo.office_code = a.undertaking_office
 left join js_sys_user creator
 on creator.user_code =a.project_manager

然后再分批次,sumAmountHandlerList 方法中传的参数就是 我们查询出的主要数据

 public List<Map<String, Object>> sumAmountHandlerList(List<Map<String, Object>> list) {
        
        // 一批次的数量,将2500 个 project_id 放在一个集合里
        int batchNumber = 2500;
 ​
         // 循环
         int iteratorCount = (int) Math.ceil((double) list.size() / batchNumber);
         for (int i = 0; i < iteratorCount; i++) {
             int startIndex = i * batchNumber;
             int endIndex = (i < iteratorCount - 2) ? (startIndex + batchNumber - 1) : (list.size() - 1);
 ​
             // 取所有的 'projectID'
             List<String> projectIds = new ArrayList<>();
             for (int j = startIndex; j <= endIndex; j++) {
                 projectIds.add((String) list.get(j).get("project_id"));
             }
 ​
           //核定借款合计,核定借款金额,子查询,需要现计算
             List<Map<String, Object>> individualBorrowList = projectReportDao.individualBorrowCollection(projectIds) ;
 ​
 ​
             // 数据处理
             for (int j = startIndex; j <= endIndex; j++) {
                 Map<String, Object> item = list.get(j);
 ​
                 // 核定借款金额(authorized)
                 item.put("authorized", 0D);
                 for (Map<String, Object> authorizedItem : individualBorrowList) {
                     if (((String) authorizedItem.get("project_id")).equals((String) item.get("project_id"))) {
                         item.put("authorized", Double.parseDouble(authorizedItem.get("authorized").toString()));
                         break;
                     }
                 }
              }

这块,主要讲一下,如何分批次。先获取到主体数据的总数,再根据一个批次里的数量,获取到需要几个批次,就可以全部加载完数据。也就是外循环几次。

    // 循环
         int iteratorCount = (int) Math.ceil((double) list.size() / batchNumber);
         for (int i = 0; i < iteratorCount; i++) {
              int startIndex = i * batchNumber;
             int endIndex = (i < iteratorCount - 2) ? (startIndex + batchNumber - 1) : (list.size() - 1);

第一次内循环是从0开始,第二次内循环就是 1 * 批次数量,依次类推。 int startIndex = i * batchNumber;

第一次内循环是从(批次数量-1) 结束,因为是从0开始的,第二次内循环就是 2500开始,到2500+batchNumber - 1 结束,

最后一次内循环就是 总数-1;

这里比较绕,梳理通了,整体就简单了,最好是带上具体的数字就简单多了。

然后就是在内循环中,根据project_id ,进行比对,相同的,将数据组合在一起。

其他方案

当时,也有考虑采取定时任务,在凌晨将数据全部算好,然后放到一张表里,这样直接查询一张表,肯定性能会更好一些。但数据会存在延迟性,当天审批过的数据,业务人员无法查看到,只有第二天定时任务完了,才可以查询。采取分批次子查询的方案查询在1s内就可以查询出来,导出时,由于数据较多,需要五六分钟(将近20M),业务人员也可以接受。

看看,大家还有什么比较好的方案?欢迎讨论。