Hibernate比SQL查询慢1000倍

che*_*ara 6 java sql database performance hibernate

我有这个设置

@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;
}
Run Code Online (Sandbox Code Playgroud)

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 ...
Run Code Online (Sandbox Code Playgroud)

直接数据库中或通过Hibernate(3.5)SQL进行的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);

}
Run Code Online (Sandbox Code Playgroud)

在这段代码中

final ResultSet rs = getResultSet( st, queryParameters.hasAutoDiscoverScalarTypes(), queryParameters.isCallable(), selection, session );
Run Code Online (Sandbox Code Playgroud)

它会使用完整的SQL命中数据库,但任何地方都不会收集结果。

然后继续进行此循环

for ( count = 0; count < maxRows && rs.next(); count++ ) {
Run Code Online (Sandbox Code Playgroud)

对于预期的300个结果中的每个结果,最终都将命中数据库以获取实际结果。

这似乎很疯狂,因为在1个查询之后它应该已经具有所有结果。休眠日志不会显示在所有时间内发出的任何其他SQL。

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

小智 7

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

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

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

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

更多详情可在这找到。 /sf/answers/2300730561/


che*_*ara 6

我终于弄清楚了这件事的真相。该问题是由 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();
Run Code Online (Sandbox Code Playgroud)

这会很快

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

似乎由于某种原因让 Hibernate 处理参数,它最终陷入了结果集的获取过程中。这可能是因为数据库上的底层查询需要更长的时间进行参数化。我最终编写了相当于 Hibernate Criteria and Restrictions 的代码,该代码直接设置上述参数。

  • 此解决方案使您面临潜在的 SQL 注入攻击。 (15认同)