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 的实体加载出来,内存占用也不可小觑。