【发布时间】:2015-06-17 19:56:12
【问题描述】:
我们最近使用 Hibernate 和 EntityManager(无 Spring)实现了 DB 绑定,以将记录写入数据库。为简单起见,我将仅讨论仅执行插入的过程的变体。 (另一个非常相似的过程会更新现有记录一次以设置状态,否则只会插入一堆记录。)
这个过程可以在每个事务中插入多达 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()。
【问题讨论】:
-
您提到 Hibernate 将所有 24 个内核都钉在一台机器上,完成插入需要多长时间?几秒钟或更多?如果所有 24 个内核都固定,则意味着您在业务逻辑中产生了大量线程。您能否详细解释一下您在插入时的多线程逻辑?
-
对于 9K,大约需要 2 分钟。 DBA 表示数据库已尽可能地进行了调整。可以有多个进程实例在不同的线程中运行,为不同的对象调用我上面包含的代码。这些线程除了 EntityManagerFactory 之外不共享任何对象。
-
我有过使用 Hibernate 在不到一秒的时间内插入包含 10000 条记录的树结构的经验。肯定有一些优化需要做。请分析您的代码以查找并修复效率低下的部分。为此,您可以使用 Yourkit。
-
您是在单个事务中执行此操作吗?你有 batch_size 选项 > 1 吗?在最初的开发阶段,我们使用 Yourkit 获得了另一个开发人员资料,并报告说在 Hibernate 部分中没有“热点”。我会再试一次。我不抱太大希望。除了下面的建议,我不知道从代码的角度我们还能做什么。请注意,在提交之前调用 merge() 会占用至少一半的执行时间。目前我不知道是 merge() 还是 commit() 或者两者都占用了这么多 CPU。
-
我不希望依赖 Hibernate 的合并,因为它会导致性能问题,因为整个过程超出了您的控制范围。您能否将加载数据和进行手动合并的过程分开?这使您可以分别对每个部分进行优化。可以使用 HQL 完成加载。根据您的模型的复杂程度,合并代码可能很简单也可能很复杂。
标签: java performance hibernate jpa transactions