首页 文章

Hibernate比sql查询慢1000倍

提问于
浏览
4

我有这个设置

@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的 identical 以及我在那里发布的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记录 do not show 在所有时间内发出的任何其他SQL .

有人有任何见解吗?我唯一的选择是通过Hibernate转到本机SQL查询 .

1 回答

  • 2

    我终于成功了解了这一点 . 问题是由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和Restrictions代码的代码,它直接如上所述设置参数 .

相关问题