java - 使用 Hibernate 执行数千次插入时 CPU 利用率高

标签 java performance hibernate jpa transactions

我们最近使用 Hibernate 和 EntityManager(无 Spring)实现了数据库绑定(bind),以将记录写入数据库。为了简单起见,我将只讨论仅执行插入的过程的变体。 (另一个非常相似的过程更新现有记录一次以设置状态,但除此之外,仅插入一堆记录。)

此过程可以在每个事务中插入多达 10,000 条记录,但平均值低于此值,可能至少是一半。我们可能会在同一 JVM 下同时在不同线程中运行该进程的几个实例。

我们遇到了一个生产问题,运行进程的服务正在占用机器上的所有 24 个核心。 (他们添加了 12 个只是为了尝试适应这一点。)我们已将这种高利用率缩小到 Hibernate。

我花了几天时间研究,除了使用 hibernate.jdbc.batch_size 和 hibernate.order_inserts 之外,找不到任何可以提高性能的方法。不幸的是,我们使用 IDENTITY 作为生成策略,因此 Hibernate 可以/不会批处理这些插入。

我花了几天时间进行研究,但在进行大量插入时没有发现任何其他性能提示。 (我见过很多关于读取、更新和删除的提示,但关于插入的提示却很少。)

我们有一个根 JobPO 对象。我们简单地调用合并,所有插入都是通过级联注释处理的。我们需要在单个事务中完成此操作。

我们只有 8 个不同的表需要插入,但记录的层次结构有点复杂。

public void saveOrUpdate(Object dataHierarchyRoot) {
    final EntityManager entityManager = entityManagerFactory.createEntityManager();
    final EntityTransaction transaction = entityManager.getTransaction();

    try {
        transaction.begin();

        // This single call may result in inserting up to 10K records
        entityManager.merge(dataHierarchyRoot);
        transaction.commit();
    } catch (final Throwable e) {
        // error handling redacted for brevity
    } finally {
        entityManager.close();
    }
}

我们只创建 EntityManagerFactory 一次。

有什么想法吗?

附加说明:

  • 没有人提示该进程使用过多内存

  • 对于仅执行插入的过程的变体,我们可以使用“persist”而不是“merge”。我们正在共享代码,因此我们进行合并。我尝试改用坚持,但没有明显的改善。

  • 我们确实有一些注释,可以在一些字段上实现双向级联。我尝试删除这些,但由于对 Hibernate 不熟悉,无法正确保存它。但据我了解,这似乎不会导致插入性能下降。我没有使用显式的“反向”设置,因为这对于插入似乎也不重要。不过,我在这两个方面都有点动摇。这方面还有改进的空间吗?

  • 我们在单个事务期间运行 SQL Profiler。似乎没有什么问题,我也没有发现改进的空间。 (有大量的 exec sp_prepexec 语句,大约与插入的记录数相同。这就是报告的全部内容。)

  • 在生产环境中表现出这种行为的代码是在 commit() 之前显式调用entityManager.flush()。我在本地环境中删除了该代码。它没有做出明显的改进,但我不会将其添加回来,因为我们没有理由调用flush()。

最佳答案

如果您为要保存的每个对象打开和关闭一个 session ,那么对于 10k 个对象,您实际上会打开和关闭 10k 个 session 、刷新 10k 次并进入数据库进行 10k 次往返。

你至少应该batch multiple entities一起:

for (Object entity: entities) {    
    if(entity.getId() == null) {
        entityManager.persist(entity);
    } else {
        entityManager.merge(entity);
    }   
    if ((i % batchSize) == 0) {
        entityManager.getTransaction().commit();
        entityManager.clear();          
        entityManager.getTransaction().begin();
    }
}
entityManager.getTransaction().commit();
em.getTransaction().commit();

在此示例中,您实际上使用了一个数据库连接,因此即使您使用连接池,您也不必获取/释放 10k 个数据库连接。达到 batchSize 阈值后, session 将被清除,从而减少 JVM 垃圾回收。

如果您在 session 中存储 10k 个实体并立即提交事务,您将遇到以下问题:

  • 数据库将持有锁更长时间,并会创建大量撤消事务日志(如果您的数据库使用 MVCC)
  • 实体不会被垃圾收集,因为它们仍然附加到 Hibernate Session

关于java - 使用 Hibernate 执行数千次插入时 CPU 利用率高,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30901190/

相关文章:

java - 从开发到生产集成 Java Web 应用程序

mysql - 选择记录,其中 id 是集合 id 之一

java - Hibernate 一对多返回多个重复对象

java - 从对象列表中查找字符串

java - 如何从处理程序中删除所有回调?

performance - Grails 3 vs 4 性能、启动时间和内存

c# - 对于 35 张图像,如何在获取 1 个特定像素的颜色时获得最佳性能?

java - 转换一个内部有不同类的对象

java - 找不到主类,程序将退出

python - 这个python函数可以向量化吗?