【问题标题】:Why is the Hibernate query.list() slow?为什么 Hibernate query.list() 很慢?
【发布时间】:2012-08-28 16:06:20
【问题描述】:

我正在使用 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));
}

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

更新:我尝试使用使用休眠调试查询的 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));

【问题讨论】:

  • 可能是一个未提交的交易阻碍了工作。
  • 您可能想要打印出您的休眠 sql。很可能有 n+1 选择问题。
  • 乔丹,我不知道有任何交易阻碍了这个查询。约翰,我确实打印了上面提到的sql,查询很好。
  • 那么初始查询可能没问题,您最终会看到额外的选择吗?或者你只看到一个查询,应该以Hibernate:开始
  • 你发现发生了什么吗?

标签: java hibernate


【解决方案1】:

我不能 100% 确定这个答案,因为调优/优化问题总是很难查明。

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

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

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

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

【讨论】:

  • 您好菲利普,感谢您的回复!我确实看过那个链接,我所有的 POJO 都有完整的构造函数。不幸的是,该解决方案不适用于我的情况。
  • 嗯,太糟糕了,我希望这会有所帮助。看起来您在故障排除方面非常彻底,我真的很想提供帮助。你能发布你的 POJO 吗?
  • 你有没有进一步研究过这个?除了这里和您引用的其他问题之外,我找不到任何提及它的内容。
【解决方案2】:

如果查询(使用show_sql)似乎没有问题,那么它可能在代码中。启动 VisualVM(JDK 附带 jvisualvm)并使用其 CPU 分析器找出哪些方法耗时最长。

【讨论】:

  • 这个对 query.list 的特殊调用是导致缓慢的罪魁祸首。希望有人可以提供有关为什么会发生这种行为以及可能的解决方法的见解
  • 我的意思是,调用 query.list() 的哪个方法是罪魁祸首?深入研究 Hibernate 代码以找到根本原因。
  • 如前所述,没有。
  • list() 方法本身做的很少,所以它不能 是根本原因:github.com/hibernate/hibernate-orm/blob/4.1/hibernate-core/src/… 原因必须在 list() 调用的方法中做真正的工作。使用分析器找出它。
  • 一个分析器是我首先遇到这个问题的方式。没有休眠日志语句表明正在发生任何其他事情。它肯定发生在 query.list 方法中
【解决方案3】:

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

这可能为时过早,因为查询执行时间不仅仅取决于查询文本。即使他们的查询是在相同的数据上运行的,你怎么知道数据库使用了相同的执行计划?你怎么知道它在磁盘缓存中获得了相同数量的缓存命中?例如,hibernate 在与数据库通信时使用准备好的语句,但您可能没有。在 Oracle 中,执行计划由查询文本缓存,因此不同的查询文本意味着新计算的执行计划。由于缓存的执行计划可能是基于不同的查询参数形成的,因此它很可能是不同的——这可能会改变执行时间的数量级。请注意,我并不是说它数据库,但我不会忽视这种可能性。

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

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

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

【讨论】:

    【解决方案4】:

    我们遇到了类似的问题,不知道是否相关。基本上,由于我们每次查询都会更新一次新的 SessionFactory,它会执行如下查询:

     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 解释了硬解析,这显然很糟糕。

    另一个可能的解决方法是在休眠中使用“原始 sql”(jdbc) 或可能的原始 sql 查询,尽管在这种特殊情况下似乎并没有解决问题...

    【讨论】:

      【解决方案5】:

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

      在我的情况下,问题在于 hibernate 使用 cross join 进行隐式连接,因此(在我看来)从数据库中获取数据后需要一些时间来构造结果(可能使用反射)。

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

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

      【讨论】:

        猜你喜欢
        • 2017-03-16
        • 2019-10-02
        • 2010-10-30
        • 2023-04-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-01
        • 2014-03-29
        相关资源
        最近更新 更多