gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-29 03:08:11 24 4
gpt4 key购买 nike

我们最近使用 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。

有什么想法吗?

补充说明:

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

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

  • 我们确实有一些注释可以在一些字段上产生双向级联。我尝试删除这些,但作为 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 数据库连接。 Session 在达到 batchSize 阈值后被清除,因此减少了 JVM 垃圾收集。

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

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

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

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com