【问题标题】:Hibernate is 1000 times slower than sql queryHibernate 比 sql 查询慢 1000 倍
【发布时间】:2015-10-07 18:34:12
【问题描述】:

我有这个设置

@Table(name ="A")
EntityA {
    Long ID;
    List<EntityB> children;
}

@Table(name ="B")
EntityB {
    Long ID;
    EntityA parent;
    EntityC grandchild;
}

@Table(name ="C")
EntityC {
    Long ID;
}

SQL 查询是这样的(我省略了不相关的细节):

select top 300 from A where ... and ID in (select parent from B where ... and grandchild in (select ID from C where ...)) order by ...

直接数据库中的 sql 查询或通过 Hibernate (3.5) SQL 的运行速度比使用 Criteria 或 HQL 来表达这一点要快 1000 倍。

生成的 SQL 与 HQL 和 Criteria 以及我在那里发布的 SQL相同。

[编辑]:更正 - sql 不相同。我没有尝试管理工作室端的 Hibernate 样式参数设置,因为直到后来我才意识到这一点 - 请参阅我的答案。

如果我将子查询分成单独的查询,那么它又快了。

我试过了

  • 删除子、父等的所有映射并仅使用 Long Id 引用 - 相同的东西,因此它与获取、懒惰、渴望相关。
  • 使用连接而不是子查询,并且在获取和加载的所有组合中都获得了相同的缓慢行为。
  • 在 ID 上设置投影而不是检索实体,因此没有对象转换 - 仍然很慢

我查看了 Hibernate 代码,它正在做一些令人震惊的事情。它有一个循环遍历所有 300 个结果并最终进入数据库。

private List doQuery(
        final SessionImplementor session,
        final QueryParameters queryParameters,
        final boolean returnProxies) throws SQLException, HibernateException {

    final RowSelection selection = queryParameters.getRowSelection();
    final int maxRows = hasMaxRows( selection ) ?
            selection.getMaxRows().intValue() :
            Integer.MAX_VALUE;

    final int entitySpan = getEntityPersisters().length;

    final ArrayList hydratedObjects = entitySpan == 0 ? null : new ArrayList( entitySpan * 10 );
    final PreparedStatement st = prepareQueryStatement( queryParameters, false, session );
    final ResultSet rs = getResultSet( st, queryParameters.hasAutoDiscoverScalarTypes(), queryParameters.isCallable(), selection, session );

// would be great to move all this below here into another method that could also be used
// from the new scrolling stuff.
//
// Would need to change the way the max-row stuff is handled (i.e. behind an interface) so
// that I could do the control breaking at the means to know when to stop

    final EntityKey optionalObjectKey = getOptionalObjectKey( queryParameters, session );
    final LockMode[] lockModesArray = getLockModes( queryParameters.getLockOptions() );
    final boolean createSubselects = isSubselectLoadingEnabled();
    final List subselectResultKeys = createSubselects ? new ArrayList() : null;
    final List results = new ArrayList();

    try {

        handleEmptyCollections( queryParameters.getCollectionKeys(), rs, session );

        EntityKey[] keys = new EntityKey[entitySpan]; //we can reuse it for each row

        if ( log.isTraceEnabled() ) log.trace( "processing result set" );

        int count;
        for ( count = 0; count < maxRows && rs.next(); count++ ) {

            if ( log.isTraceEnabled() ) log.debug("result set row: " + count);

            Object result = getRowFromResultSet( 
                    rs,
                    session,
                    queryParameters,
                    lockModesArray,
                    optionalObjectKey,
                    hydratedObjects,
                    keys,
                    returnProxies 
            );
            results.add( result );

            if ( createSubselects ) {
                subselectResultKeys.add(keys);
                keys = new EntityKey[entitySpan]; //can't reuse in this case
            }

        }

        if ( log.isTraceEnabled() ) {
            log.trace( "done processing result set (" + count + " rows)" );
        }

    }
    finally {
        session.getBatcher().closeQueryStatement( st, rs );
    }

    initializeEntitiesAndCollections( hydratedObjects, rs, session, queryParameters.isReadOnly( session ) );

    if ( createSubselects ) createSubselects( subselectResultKeys, queryParameters, session );

    return results; //getResultList(results);

}

在这段代码中

final ResultSet rs = getResultSet( st, queryParameters.hasAutoDiscoverScalarTypes(), queryParameters.isCallable(), selection, session );

它使用完整的 SQL 访问数据库,但没有在任何地方收集任何结果。

然后它继续通过这个循环

for ( count = 0; count < maxRows && rs.next(); count++ ) {

对于预期的 300 个结果中的每一个,它最终都会访问数据库以获取实际结果。

这看起来很疯狂,因为它应该已经在 1 次查询后获得了所有结果。 Hibernate 日志不显示在此期间发出的任何其他 SQL。

有人有什么见解吗?我唯一的选择是通过 Hibernate 进行本机 SQL 查询。

【问题讨论】:

  • 这取决于 fetch 大小,还比较 SQL 执行时间和 hibernate 的结果是比较苹果和椰子。 Hibernate 做了更多工作,例如将结果转换为对象,这需要时间取决于您的映射,并且结果的数量可能需要很多时间。
  • 我编辑了这个问题,我尝试在 ID 上设置投影以及删除映射,所以它与实体转换无关,它实际上是由连接/子查询触发的
  • 几个月前 Hibernate 也遇到了同样的问题。最终放弃了它,因为它需要在数据库中超过 10 条记录。不要费心在网上搜索洞察力或解决这个问题我花了几天时间做这件事,但一无所获。最佳解决方案;对于任何需要任何类型的密集数据库操作的东西,都可以自己吸收并编写查询代码,然后自己将数据转换为对象。通过使用数据密集型应用程序,我个人将运行时间减少了约 500%。
  • 这就是您为 ObfuscateRelationalM模型支付的价格
  • Hibernate 应该如何收集结果集中返回的数据而不遍历结果集?当然是这样做的。您的手工编写的 JDBC 代码也必须这样做,除非您当然想执行查询而不关心它返回的内容。

标签: java sql database performance hibernate


【解决方案1】:

我终于设法弄明白了。该问题是由 Hibernate 将参数设置与涉及子查询的实际 SQL 查询分开设置的。所以无论是否使用原生 SQL,如果这样做,性能会很慢。例如这会很慢:

String sql = some sql that has named parameter = :value
SQLQuery sqlQuery = session.createSQLQuery(sql);
sqlQuery.setParameter ("value", someValue);
List<Object[]> list = (List<Object[]>)sqlQuery.list();

这会很快

String sql = some native sql where parameter = 'actualValue'
SQLQuery sqlQuery = session.createSQLQuery(sql);
List<Object[]> list = (List<Object[]>)sqlQuery.list();

似乎由于某种原因让 Hibernate 处理参数最终会卡在 resultSet 获取中。这可能是因为数据库上的基础查询需要更长的时间进行参数化。我最终编写了与上面一样直接设置参数的 Hibernate Criteria and Restrictions 代码。

【讨论】:

  • 此解决方案使您面临潜在的 sql 注入攻击。
  • 谢谢,注意到这个问题
  • 您说您使用的是 Hibernate 3.5,您是否也尝试过使用更新的版本,例如 4.3?我自己没有遇到任何这些性能问题 - 虽然我必须承认我不直接使用 Hibernate API,但我使用 JPA。
  • 我没有尝试过更高版本的hibernate,但我确认减速不在hibernate代码中,而是在Hibernate代码提供命名参数时触发的数据库端。这取决于您正在执行的查询以及您注意到效果之前数据库中的总行数。
  • 您使用的是 Sql Server 吗?可能您的问题与 jTDS 驱动程序和 varchar 参数有关。有时你认为hibernate在做where parameterr = 'actualValue'但它在做where parameterr = N'actualValue'
【解决方案2】:

我们注意到我们的系统中有类似的行为。

并且还遇到了使用硬编码参数而不是使用setParameter() 来编写查询可以解决问题。

我们使用的是 MS SQL Server,经过进一步调查,我们注意到问题的根本原因是 sql server 驱动程序的默认配置,它将查询参数作为 unicode 传输。这导致我们的索引被忽略,因为它们基于查询列上的 ascii 值。

解决方案是在 jdbc url 中设置此属性:sendStringParametersAsUnicode=false

更多细节可以在这里找到。 https://stackoverflow.com/a/32867579

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-01-12
    • 2015-01-03
    • 1970-01-01
    • 2015-09-09
    • 2015-05-23
    • 2021-05-10
    • 2014-09-12
    • 2014-01-08
    相关资源
    最近更新 更多