Hibernate:QueryImpl 列表方法性能

Hibernate : QueryImpl list method Performance

对于我们的报告 UI,我们查询会话对象并将其列在屏幕中。要查询数据,我们使用 Hibernate 和一个 Generic Dao implementation. Before use Dynatrace we always blame database about this query but after start to use DynaTrace it shows us that the bottleneck is in the code at QueryImpl.list method. We have really bad performance both Prod and Dev, The total count records are about 2M in PROD and it takes 75 seconds(yes more than 1 minute :( ) Below screenshots show the Dynatrace Screenshots which show us problem is in Hibernate QueryImpl list method. I checked the application in DEV environment with 500K records and it takes 30 seconds in DEV too and same methods takes the 28 seconds at this query. I track the application get heap dump and analyze in dynatrace and jvisualvm. Also check thread dumps both at samurai 和 dyntarace 但找不到任何锁定的线程或很多特殊的 class 实例。我分享了 dynatrace 纯路径截图和我们使用 Spring @Transactional 注释的方法调用。

ReadOnly.java

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Transactional(readOnly = true)
public @interface ReadOnly {

}

SessionService.java

@Override
@ReadOnly
public SearchResult<Session> getReport(ISearch search) {
       return sessionDao.searchAndCount(search);
}

SessionDao.java

public <RT> SearchResult<RT> searchAndCount(ISearch search) {
       if (search == null) {
            SearchResult<RT> result = new SearchResult<RT>();
            result.setResult((List<RT>) getAll());
            result.setTotalCount(result.getResult().size());
            return result;
        }
      return searchAndCount(persistentClass, search);
}

我花了 2-3 天的时间来调查这个问题,我真的很想知道原因。

编辑: 我从 2M table 查询 50 条记录,我使用分页方法,在屏幕截图中我提到它在数据库中需要 2.5 秒,但它花费了 75内存中的秒 QueryImpl 列表方法。所以我已经从 2M table 中查询了 50 条记录,这些记录在日期字段中有索引。请检查屏幕截图红色矩形。

首先感谢@M.Deinum 的评论帮助我们解决了这个问题。问题在下面的 SO 问题中描述:

  • Question and Accepted Answer describe the problem
  • Another related question and answer
  • Accepted answer made useful suggestion

在我的例子中,首先 运行 并在下面的 PROD 查询中花费 75 秒。

SessionWithJOINFETCH.sql

SELECT
    session0.*,
    cardholder0.*,
    transaction0.*
FROM
    MSU_SESSION session0 LEFT OUTER JOIN MSU_CARD_HOLDER cardholder0
        ON session0.card_holder_id = cardholder0_.id LEFT OUTER JOIN MSU_TRANSACTION transaction0
        ON session0.id = transaction0_.session_id
WHERE
    (
        session0.status IN(
            ? ,
            ?
        )
    )
    AND(
        session0.sessionType IS NULL
        OR session0.sessionType IN(
            ? ,
            ?
        )
    )
    AND session0.session_create_timestamp >= ?
    AND session0.session_create_timestamp <= ?
ORDER BY
    session0.session_create_timestamp DESC

而且在日志中我看到警告:

firstResult/maxResults specified with collection fetch; applying in memory!

这意味着我们在内存中使用分页(for Oracle rowNum)。可以看到一开始runnig sql它没有任何rowNum,它根据条件获取所有数据然后"firstResult/maxResults specified with collection fetch; applying in memory!"在内存中应用firstResult/maxResult需要时间。

原因是我们正在使用 JOIN FETCHsetMaxResult。根据 Hibernate 论坛和上面的 SO 问题链接,它是一个休眠功能,因此在删除 [=53= 下面的关系后,你不应该将 setMaxResultJOIN FETCH(至少与 1-N 关系)一起使用] 由hibernate创建,现在性能更好了。

AfterRemoveJOINFETCH.sql

SELECT
    *
FROM
    (
        SELECT
             session0_.*
        FROM
            MSU_SESSION session0_
        WHERE
            (
                session0_.status IN(
                    ? ,
                    ?
                )
            )
            AND(
                session0_.sessionType IS NULL
                OR session0_.sessionType IN(
                    ? ,
                    ?
                )
            )
            AND session0_.session_create_timestamp >= ?
            AND session0_.session_create_timestamp <= ?
        ORDER BY
            session0_.session_create_timestamp DESC
    )
WHERE
    rownum <= ?

我不知道为什么它创建了一个内部 sql 但它的性能比以前更好,现在您可以看到 rownum 被添加到查询中并且分页是在数据库级别完成的。

firstResult/maxResults specified with collection fetch; applying in memory!

警告也消失了