【问题标题】:Hibernate queries slow down drastically after an entity is loaded in the session在会话中加载实体后,休眠查询会显着减慢
【发布时间】:2010-12-19 22:46:03
【问题描述】:

我正在使用 Hibernate EntityManager,并且在我的 Hibernate 查询中遇到了奇怪的减速。看看这段代码:

public void testQuerySpeed() {
    for(int i = 0; i < 1000; i++) {
        em.createNativeQuery("SELECT 1").getResultList();
    }
}

这在我的机器上运行大约 750 毫秒。考虑到它只是选择一个常数整数,速度并不快,但可以接受。我的问题出现在我启动查询之前在我的 EntityManager 会话中加载任何实体的那一刻:

public void testQuerySpeed() {
    CommercialContact contact = em.find(CommercialContact.class, 1890871l);

    for(int i = 0; i < 1000; i++) {
        em.createNativeQuery("SELECT 1").getSingleResult();
    }
}

em.find() 速度很快,但运行时 1000 次查询增加了十倍以上,大约 10 秒。如果我在em.find() 后面加上em.clear(),问题就会再次消失,运行时间会回到 750 毫秒。

我在这里使用了本机查询,但是 HQL 查询也存在问题。每当实体处于 EntityManager 会话中时,似乎所有查询至少需要 70 毫秒。

在生成需要 n+1 个查询的列表时,这种性能下降确实对我们造成了伤害。

我已经测试了最新的 Hibernate 3.5 beta,并且遇到了完全相同的问题。有没有人看到这个问题,或者有任何解决方法的想法?

我正在使用 PostgreSQL 8.3,使用资源本地事务(在 Tomcat 中运行)。使用内置连接池,但使用 C3P0 没有区别。

【问题讨论】:

  • 你分析了吗?大部分时间都花在了哪里?您是否定义了任何侦听器/拦截器/回调方法?

标签: hibernate hql


【解决方案1】:

我基本上遇到了同样的问题(循环内的查询)。我使用 JProfiler 进行了分析验证......我感兴趣的方法的执行花费了 572 秒,而休眠脏检查需要 457 秒(大约 80%)。很神奇,不是吗?我必须说我有很多由 EntityManager 管理的实体。如果我在有问题的代码中引入 em.flush()/em.clear() 或 em.setFlushMode(FlushModeType.COMMIT),性能问题就会消失。

分析器结果可在http://img1.imagilive.com/0110/hibernate_dirty_checking_bad_perfomances0ce.png获得

【讨论】:

    【解决方案2】:

    您执行的本机查询没有说明它将触及什么,因此 Hibernate 将不得不(为了一致性起见)flush() 对它知道的所有表中的所有数据(并且您的单个 find() 可能已经获取了不止一个对象,所以这可能不是一个简单的操作)。

    要优化这一点,请确保您使用 SQLQuery.add* 方法来定义查询实际执行的操作。在这种情况下 query.addSynchronizedQuerySpace("bogustablename") 应该告诉 Hibernate 这个查询只是来自没有特定表的标量数据。

    【讨论】:

      【解决方案3】:

      我还必须建议使用 JVM 分析器来查看时间的去向。为 Hibernate 会话打开 SQL 语句日志记录也可能没有什么坏处,只是为了确保您运行的 SQL 没有超出您的想象。

      这里首先想到的是 Hibernate Session 的“刷新”行为。您是否在 Session 上明确设置了特定的刷新模式?如果没有,那么您将获得“自动”刷新,这将检查您在会话中拥有的对象以确定是否存在需要“刷新”回数据库的内存更改(在事务内部,当然)。

      我想首先尝试看看它是否有任何效果的最简单的方法是修改您上面显示的测试代码,以指定您希望在提交数据库事务时手动进行刷新:

      public void testQuerySpeed() {
          em.setFlushMode(FlushModeType.COMMIT); // assuming you're using JPA annotations
          CommercialContact contact = em.find(CommercialContact.class, 1890871l);
      
          for(int i = 0; i < 1000; i++) {
              em.createNativeQuery("SELECT 1").getSingleResult();
          }
      }
      

      我想的另一个想法是询问您是否可以在单独的 EntityManager 中执行批量任务,如果您只是执行 UPDATE 或 INSERT,这可能会起作用。

      【讨论】:

      • 确实,在我的代码中刷新模式设置为 AUTO。我已经在我们的报告代码上将刷新模式设置为 COMMIT,无论如何我们不会在那里创建任何实体。这有效地解决了性能问题。奇怪的是,仅对一个实体检查脏对象就需要大约 60 毫秒。非常感谢您的洞察力!
      • 天啊,非常感谢,Alexander 和 BryandD!你们俩救了我的命! :) 我在这里遇到了完全相同的问题,其中包含一个包含多个搜索和批量插入/更新的循环的事务。经过一周更改算法、谷歌搜索和分析后,我的性能问题通过一个简单的方法解决了:em.setFlushMode(FlushModeType.COMMIT);在此之前,整个过程大约需要 28 分钟,其中 95% 花费在 getResultList() 上。之后,一切顺利,只需大约 2 分钟。我想知道为什么 jpa/hibernate 不选择 FlushModeType.COMMIT EntityManager 的默认 FlushMode ...
      猜你喜欢
      • 2014-09-12
      • 2013-03-18
      • 2017-08-22
      • 2020-02-16
      • 1970-01-01
      • 1970-01-01
      • 2021-01-30
      • 2014-01-03
      • 1970-01-01
      相关资源
      最近更新 更多