咋整!报表查询太慢了! | Java Debug 笔记

838 阅读3分钟

本文正在参加「Java主题月 - Java Debug笔记活动」,详情查看活动链接

一 查询优化

优化是一个程序员的必经之路,更多的数据量,更好的查询方式,这些都会涉及到优化,常规的优化有很多,现在来说说项目中报表的一次实战优化。

二 故事背景

产品阿产最近找来开发杨乐乐,乐乐呀,我们系统中的进口收退款报表客户那边反馈需要将查询条件放开要查一年的数据,乐乐问,这个有什么问题吗?阿产说这个功能查询很慢,所以之间通过业务的手段进行了控制,客户如果通过时间来查询最多只能查3个月的数据,于是乐乐自己在测试环境实际操作了一下,发现查询600条数据就要11s,的确好慢。就这样一次优化之旅开启了。

三 查询慢问题定位

乐乐发现报表的数据是组装的,而数据的来源是三个数据库表,他们并没有通过left join的方式,而是查询出第一个表之后,再通过第一个表数据通过关联的id查询另外两个表的id。代码如下

 public List<List<InboundSettlementReport>> anyData(List<InboundSettlementReport> inboundSettlementReportList) {
        List<List<InboundSettlementReport>> outList = Lists.newArrayList();
        if (ObjectUtils.isNotEmpty(inboundSettlementReportList)) {
            Map<String, List<InboundSettlementReport>> map = Maps.newHashMap();
            Map<String,InboundBooking> bookingMap = Maps.newHashMap();
            Map<String,InboundEir> eirMap = Maps.newHashMap();
            // 同一船公司分组
            for (InboundSettlementReport entity : inboundSettlementReportList) {
                entity.calculateRefundmentAmount();
                if (map.get(entity.getCarrier()) == null) {// 修改为以船公司 分组
                                        List<InboundSettlementReport> inList = Lists.newArrayList();
                                        inList.add(entity);
                                        map.put(entity.getCarrier(), inList);
                } else {
                    map.get(entity.getCarrier()).add(entity);
                }

                InboundBooking booking =  bookingMap.get(entity.getBookingId());
                if(booking == null ) {
                    booking = bookingService.findById(entity.getBookingId());
                    if(booking != null) {
                        bookingMap.put(booking.getId(),booking);
                    }
                }
                if(booking != null) {
                    entity.setVessel(booking.getVessel());
                    entity.setVoyage(booking.getVoyage());
                    entity.setEtdArrivedDate(booking.getEtdArrivedDate());
                }

                InboundEir eir = null;
                if(entity.getEirId() != null) {
                    eir = eirMap.get(entity.getEirId());
                    if(eir == null) {
                        eir = eirService.findById(entity.getEirId());
                        eirMap.put(eir.getId(), eir);
                    }
                }
                if(eir != null) {
                    //entity.setCtnEffectiveBack(eir.getCtnEffectiveBack());
                    //entity.setCntFreeDay(eir.getCntFreeDay());
                    entity.setDocumentsSizeType(eir.getDocumentsSizeType());
                }

            }

            // 同船公司费用合计
            anyFeeDate(map);
            outList.addAll(map.values());

        }
        return outList;
    }

理论上,通过id查询查询的数据应该会很快,但是的确是这种方式使查询的性能消耗极大,那是为什么呢?

四 原因分析

通过id查的一定走索引咋就慢呢,原因就是项目每次查询都需要从数据库连接池来获取连接来进行查询,那么600条数据 就要查询1200次关联表,这样不断的获取连接本身就是需要时间,也就是因为如此,所以产生了性能直线下降。

五 解决方案

  1. 我们的项目是通过Spring data Jpa的框架进行的查询,采用实体的@ManyToOne是否可以解决呢?如这样

    @ManyToOne(cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
            @JoinColumn(name = "last_modified_by", referencedColumnName = "code", insertable = false, updatable = false)
            private User user;
    

    @ManyToOne可以在实体中进行关联查询,使用这种方式后发现性能并没有提升,打印出来的sql日志也是通过id来进行关联查询,和之前是一致的。

  2. 通过视图的方式,在视图中将各个表通过left join的形式进行关联,最终的结果是600条的数据量查询不到2s,性能得到极大提升

  3. 使用fetch进行查询,这种方式可以在代码中指定left join,最终和视图来查询的sql是一样的,例如

     public static final String FIND_BY_FREE_FILTER =
         "SELECT DISTINCT ber FROM BookingEirRef ber LEFT JOIN FETCH ber.settlements s";
    

六 结语

我们常说查询的优化是sql本身存在问题,如索引未生效,但是很多时候优化的手段并不是在sql阶段,可能是代码本身的不合理,在实际的开发中要注意查询的设计,重视代码的管理。