为什么Hibernate query.list()会变慢?

时间:2012-08-28 16:06:21

标签: java hibernate

我正在使用Hibernate 4.1.6并且遇到列表构建速度的问题。我正在运行以下查询。

public void doQuery(final Baz baz){
  final Query query = getSessionFactory().getCurrentSession().createQuery(
          "select c.id, foo.someValue from Foo as foo "+
          "join foo.a as a"+
          "join foo.b as b "+
          "join b.c as c "+
          "where baz=:baz"
          );
  query.setParameter("baz", baz);
  Long start=System.currentTimeMillis();
  final List<Object[]> list = query.list();
  Long end=System.currentTimeMillis();
  System.out.println((end-start));
}

我设置了hibernate调试以获取发送到数据库的实际查询。我直接在数据库中运行了该查询,并在0.015毫秒内返回了23,000行。所以,我猜测查询不是问题。上面的示例显示创建该列表需要大约32秒。有什么办法可以加快速度吗?

更新:我尝试使用hibernate调试查询的createSQLQuery()方法,它的运行速度和createQuery()方法一样慢。

更新:我尝试使用无状态会话,但运行速度一样慢。

更新:我输出了一些统计信息(将hibernate.generate_statistics标志设置为true)但对我来说没什么好看的:

Hibernate SessionFactory Statistics [
    Number of connection requests[4]
    Number of flushes done on the session (either by client code or by hibernate[3]
    The number of completed transactions (failed and successful).[3]
    The number of transactions completed without failure[3]
    The number of sessions your code has opened.[4]
    The number of sessions your code has closed.[3]
    Total number of queries executed.[4]
    Time of the slowest query executed.[28258]
    the number of collections fetched from the DB.[6]
    The number of collections loaded from the DB.[6]
    The number of collections that were rebuilt[0]
    The number of collections that were 'deleted' batch.[0]
    The number of collections that were updated batch.[0]
    The number of your objects deleted.[0]
    The number of your objects fetched.[1]
    The number of your objects actually loaded (fully populated).[204]
    The number of your objects inserted.[1]
    The number of your object updated.[0]
]

Hibernate SessionFactory Query Statistics [
    total hits on cache by this query[0]
    total misses on cache by this query[0]
    total number of objects put into cache by this query execution[0]
    Number of times this query has been invoked[1]
    average time for invoking this query.[28258]
    maximum time incurred by query execution[28258]
    minimum time incurred by query execution[28258]
    Number of rows returned over all invocations of this query[23303]
]

更新:从本机查询的ScrollableResults执行next()时,我看到同样的缓慢。请注意,我在循环中什么都不做。

    ScrollableResults results = query.scroll();
    Long start=System.currentTimeMillis();
    while (results.next()) {
       //do nothing
    }
    Long end=System.currentTimeMillis();
    System.out.println((end-start));

5 个答案:

答案 0 :(得分:7)

我对这个答案并不是100%肯定,因为调整/优化问题总是难以确定。

但是,基于您打开show_sql,提取查询并直接针对数据库运行它并通过Hibernate Query查看亚秒结果与执行时间的事实,我关注的是Hibernate正在构建和保护query.list()调用产生的对象。

这是另一个在Hibernate中提到类似Query性能问题的用户,并通过在POJO中添加完全便利构造函数(接受每个字段的值的构造函数)来看到显着的性能提升:Simple hibernate query returning very slowly

听起来他们偶然发现了这个问题,并且没有清楚地理解为什么会这样。有人猜测Hibernate必须使用反射来检测属性。我很好奇自己,并且正计划深入挖掘Hibernate的源代码,以便在有机会时更好地理解这一点。与此同时,您可能希望考虑添加这些完整的构造函数以及所有POJO类属性的参数,并查看这是否有所不同。

请让我知道你发现了什么,因为我对Hibernate性能优化非常感兴趣。谢谢!

答案 1 :(得分:4)

如果查询(show_sql)似乎没有问题,那么也许它在代码中。启动VisualVM(随JDK一起提供jvisualvm)并使用其CPU分析器找出哪些方法花费的时间最长。

答案 2 :(得分:1)

  

我直接在数据库中运行了该查询,并在0.015毫秒内返回了23,000行。所以,我猜测查询不是问题。

这可能为时过早,因为查询执行时间比查询文本要多得多。即使他们的查询是在相同的数据上运行,您如何知道数据库使用相同的执行计划?你怎么知道它在磁盘缓存中获得了相同数量的缓存命中?例如,hibernate在与数据库交谈时使用预准备语句,但您可能没有。在Oracle中,执行计划由查询文本缓存,因此不同的查询文本意味着新计算的执行计划。由于高速缓存的执行计划可能是基于不同的查询参数形成的,因此它可能完全不同 - 并且可以将执行时间改变数量级。请注意,我并不是说它数据库,但我不打算忽视这种可能性。

因此,您应该做的第一件事是衡量数据库或JVM中运行的东西是否在浪费所有时间。一种简单的方法是在执行查询时观察JVM的CPU消耗。如果它明显少于一个线程,那么JVM正在等待某些东西 - 可能是数据库。

如果是数据库,请使用数据库的优化工具来捕获执行计划和其他相关的性能指标。

如果它在JVM中,请使用Profiler查明性能瓶颈。

答案 3 :(得分:0)

我们遇到了类似的问题,如果相关则不知道。基本上,因为我们每个查询都要新建一次SessionFactorys,所以它正在进行如下查询:

 select streamref0_.UUID as UUID145_, streamref0_.Tape_TapeId as Tape2_145_ from StreamRefToTape streamref0_ where streamref0_.UUID=?

你会注意到那里的大数字。事实证明,每个新会话工厂增加一次。无论如何,这导致oracle花费所有时间为每个查询做一个新计划(它报告cpu几乎全部在“硬解析”时间产生新计划 - 我想Oracle生成的计划很慢,它还没有看到之前?)。在这种特殊情况下的修复是每次只使用相同的工厂而不是新工厂。另请参阅Hibernate produce different SQL for every query

http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:2588723819082解释了硬解析,这显然很糟糕。

另一个可能的解决方法是在hibernate中使用“raw sql”(jdbc)或者可能是原始的sql查询,虽然这似乎没有解决这个特定情况下的问题......

答案 4 :(得分:0)

我不确定,但是我在当前的项目中遇到了这个问题。

在我的情况下,问题在于休眠状态使用cross join进行隐式连接,因此(以我的观点)从数据库中获取数据以构造结果(也许使用反射)需要花费一些时间。 / p>

我的解决方案是显式使用内部联接。

对于您的问题,我认为您可以显式使用inner join,而不仅仅是join