You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Hibernate批量插入后后续关联实体插入性能骤降问题排查

问题

使用Hibernate 5.2.5.Final执行数千条Booking的批量插入操作,每条Booking关联的Budget已存在于数据库中,批量插入代码如下:

public List<Long> batchInsertBookings(List<Booking> bookings, String resource) {
  List<Long> generatedIds = new ArrayList<>();
  router.setDataSource(resource);

  try (StatelessSession statelessSession = em.unwrap(Session.class).getSessionFactory().openStatelessSession()) {
    int batchSize = 20;
    int numberOfEntities = bookings.size();
    UserTransaction userTransaction = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction");
    userTransaction.begin();

    for (int i = 0; i < numberOfEntities; i += batchSize) {
      int endIndex = Math.min(i + batchSize, numberOfEntities);
      List<Booking> batch = bookings.subList(i, endIndex);

      // Save the batch data
      for (Booking entity : batch) {
        statelessSession.insert(entity);
      }

      // Retrieve the generated IDs after flush
      List<Long> batchIds = batch.stream()
              .map(IObject::getId)
              .collect(Collectors.toList());
      generatedIds.addAll(batchIds);
    }
      userTransaction.commit();
    } catch (Exception e) {
      Log.debug(e.getMessage(), getClass().getName());
    }
    return generatedIds;
  }

该插入方法在新线程中执行,批量插入可正常完成且数据已入库,后续查询操作性能无明显变化,但插入带元素集合或关联的实体(如包含对应数千条Booking的字符串集合diff的ChangeLog)时,性能骤降:每个字符串元素的持久化耗时约1秒,而未执行批量插入时该操作可快速完成。

实体关联结构

1 Account   (account)      <->  (mainBudget) Budget 0..1
0..1 Budget (parentBudget) <->  (subBudget) Budget *
1 Budget (budget) <-> (bookings) Booking *

关联映射示例

Booking.java:

@ManyToOne(fetch=FetchType.LAZY, optional= false)
@JoinColumn(name="budget_id")
@BatchSize(size = 50)
private Budget budget;

Budget.java:

@OneToMany(targetEntity = Booking.class, mappedBy = "budget")
@Fetch(value = FetchMode.SUBSELECT)
private List<Booking> booking; 

已尝试的优化方案

  • 为所有关联添加@BatchSize注解解决N+1查询问题;
  • 配置Hikari连接池;
  • 尝试不同批量插入方式(普通Session、@Transactional+EntityManager等);
  • 简化Booking实体关联结构后性能恢复;
  • 提前加载Booking关联避免懒加载异常;
  • 移除级联操作、关闭连接池等测试。

附上相关persistence.xml配置、批量插入及后续插入的SQL日志、Booking表结构等信息,请问该性能问题的原因是什么?

原因分析
  1. StatelessSession的无特性导致缓存一致性失效
    StatelessSession是无状态会话,不会将插入的Booking实体纳入Hibernate一级缓存,也不会自动更新关联实体(如Budget的booking集合)的二级缓存状态。后续插入ChangeLog时,Hibernate处理关联的Booking集合时,会因为Budget二级缓存中保留的是批量插入前的旧关联数据,触发大量无效的缓存查询或全表扫描,每个元素持久化都要进行耗时的数据库交互。

  2. FetchMode.SUBSELECT的触发逻辑冲突
    Budget的booking集合使用@Fetch(FetchMode.SUBSELECT),后续操作加载Budget实体时,Hibernate会生成子查询获取所有关联的Booking。但批量插入的Booking未被纳入缓存,这个子查询会在处理ChangeLog的每个元素时重复执行,再加上数据量剧增(数千条),子查询的执行和结果处理耗时被放大,直接导致每个元素持久化耗时飙升。

  3. 跨线程事务的缓存上下文隔离
    批量插入在新线程中执行,该线程的StatelessSession事务提交后,后续操作线程的Hibernate缓存无法感知数据变化。Hibernate的缓存一致性机制会被迫进行大量脏检查或数据重新加载,尤其在处理关联集合时,这种检查被无限放大,造成性能骤降。

  4. 关联完整性检查的叠加开销
    即便移除了级联操作,Hibernate处理关联实体时仍会进行关联完整性检查。当ChangeLog关联大量Booking时,Hibernate需要逐个验证这些Booking的关联状态(如对应的Budget是否存在),而StatelessSession插入的Booking未进入缓存,每次验证都要查询数据库,叠加后形成巨大的性能开销。

内容的提问来源于stack exchange,提问作者lasbr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 11:06:34