Hibernate 大数据量查询优化
Hibernate 大数据量查询优化
0. 背景
每天的工单接近 1500,派工单接近 7w 左右,需要将两天的派工单查询出来进行处理,如果通过一个 IN 查询的方式,平台限制了单次查询只能返回 2w 的数据量
1. 解决方式 1
通过循环的方式分批查询,先查询总的数据量 count,计算需要分批的次数,而后循环
List<UmppTaskOrder> tos = new ArrayList<>();
List<String> orders = Lists.newArrayList("PO-20191107E1", "PO-20191107E2", "PO-20191107E3", "PO-20191107E4", "PO-20191107E5");
String hql = " from UmppTaskOrder t where t.uda4 in :orders";
BppCommonDao dao = SpringContextHolder.getBean("bppCommonDao");
long s1 = System.currentTimeMillis();
MestarLogger.info("循环分批:");
IntStream.iterate(0, o -> o + 1).limit(10).forEach(i -> {
MestarLogger.info("当前批次【" + i + "】" + TimeUtils.dateToString(new Date()) + " Transaction:" + dao.getCurrentSession().getTransaction());
List<UmppTaskOrder> tmp = dao.createQuery(hql).setParameterList("orders", orders).setFirstResult(i * 10000).setMaxResults(10000).list();
tos.addAll(tmp);
MestarLogger.info("当前批次【" + i + "】" + TimeUtils.dateToString(new Date()) + " 查询返回结果:" + tmp.size());
});
long s2 = System.currentTimeMillis();
MestarLogger.info("总计返回结果:" + tos.size() + " 时间:" + (s2 - s1) + "毫秒");
2. 解决方式 2
按照以上方式,假设单次查询时间为 t 秒,循环分批的情况下,只能说把结果查询出来,但累计时间还是要 batch*t 秒左右,直接考虑将 Stream 转 parallel,通过多线程调用,又会出现session is closed的错误。给每个线程单独开启 session,时间比循环处理的方式快一半以上。
问题分析:
SessionFactory 负责创建 Session,SessionFactory 是线程安全的,多个并发线程可以同时访问一个 SessionFactory 并从中获取 Session 实例。而 Session 并非线程安全。当线程开启时,session 开启,但多个线程拿到的是同一个 service 同一个 session,当某个线程结束时,session 也就结束了,而其他线程还在运行中。
List<UmppTaskOrder> tos = new ArrayList<>();
List<String> orders = Lists.newArrayList("PO-20191107E1", "PO-20191107E2", "PO-20191107E3", "PO-20191107E4", "PO-20191107E5");
String hql = " from UmppTaskOrder t where t.uda4 in :orders";
BppCommonDao dao = SpringContextHolder.getBean("bppCommonDao");
SessionFactory sessionFactory = dao.getCurrentSession().getSessionFactory();
long s1 = System.currentTimeMillis();
IntStream.iterate(0, o -> o + 1).limit(10).parallel().forEach(i -> {
Session session = sessionFactory.openSession();
MestarLogger.info("当前批次【" + i + "】" + TimeUtils.dateToString(new Date()) + " Transaction:" + session.getTransaction());
List<UmppTaskOrder> tmp = session.createQuery(hql).setParameterList("orders", orders).setFirstResult(i * 10000).setMaxResults(10000).list();
tos.addAll(tmp);
session.close();
MestarLogger.info("当前批次【" + i + "】" + TimeUtils.dateToString(new Date()) + " 查询返回结果:" + tmp.size());
});
long s2 = System.currentTimeMillis();
MestarLogger.info("并行分批总计返回结果:" + tos.size() + " 时间:" + (s2 - s1) + "毫秒");
3. 继续优化
最理想的情况下是数据库层进行汇总统计的处理,但因为汇总的逻辑需要代码中去循环处理,只能在程序中查询出来处理;最佳的方式是定义一个 BO 对象,只查询需要的字段信息,否则 10 几 w 的实体加载出来,内存占用也不可小觑。