7

我有以下代码,它从 Oracle 数据库的单个表中执行简单查询。

entityManager.createQuery(
        "SELECT a FROM " + Person.class.getSimpleName() 
        + " a WHERE lower(a.firstName) = '" + firstName + "'")
        .getSingleResult();

Hibernate 生成以下 sql:

select
        * 
    from
        ( select
            person0_.id as id75_,
            person0_.FIRSTNAME as FIRSTNAME75_,
            person0_.LASTNAME as LASTNAME75_
        from
            PERSONS person0_ 
        where
            lower(person0_.FIRSTNAME)='john' ) 
    where
        rownum <= ?

出于性能原因,我们的 DBA 建议此查询应该更简单。如何使休眠以简化查询,如下所示:

select ID, FIRSTNAME, LASTNAME from PERSONS 
where lower(FIRSTNAEM) = 'john' and rownum <= 1

谢谢

4

4 回答 4

8

我刚刚查看了explain plan与您类似的查询,并且两个查询的计划完全相同,所以我不确定您的 DBA 建议的性能原因是什么。

包装查询select * from ( ... ) where rownum = 1引入了一个 STOPKEY,它在一行后停止内部查询。Oracle 知道您实际上并不希望从子查询中获取所有结果,然后只获取第一行。

如果不修改 hibernate 源代码本身,就不可能更改 Hibernate 生成的查询。

请注意,当您尝试引入一个ORDER BY子句时,这种嵌套是必要的原因变得显而易见:

select ID, FIRSTNAME, LASTNAME 
  from PERSONS 
 where lower(FIRSTNAME) = 'john' 
   and rownum <= 1
 order by LASTNAME

产生不同的结果

select * from (
    select ID, FIRSTNAME, LASTNAME 
      from PERSONS 
     where lower(FIRSTNAME) = 'john' 
     order by LASTNAME)
  where rownum <= 1

因为在....where rownum之前应用order by clause

编辑:

作为参考,这里是解释计划的输出,这两个查询完全相同:

---------------------------------------------------------------------------------
| Id  | Operation          | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |            |     1 |   112 |     2   (0)| 00:00:01 |
|*  1 |  COUNT STOPKEY     |            |       |       |            |          |
|*  2 |   TABLE ACCESS FULL| TABLE_NAME |     1 |   112 |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------------

可以通过放置功能索引来提高性能,lower(FIRST_NAME)但两个查询使用的索引完全相同。

于 2013-03-15T10:14:44.337 回答
2

我强烈建议您使用查询参数:

Query query = entityManager.createQuery("SELECT a FROM "
    + Person.class.getSimpleName() 
    + " a WHERE lower(a.firstName) = :name");
query.setParameter("name", firstName);
return query.getSingleResult();

这有两个重要原因:

  • 您可以防止 SQL 注入
  • 您允许 SQL 服务器缓存已解析的查询,从而提高后续执行性能

考虑到

select * from (...) where rownum <= ?

包装器:这完全不消耗性能。你可以忽略它。

于 2013-03-15T10:27:16.073 回答
0

我很惊讶,没有人建议将其重写为原生查询,而不是坚持使用 Hibernate 生成的内容。我有同样的问题,虽然我的与性能无关,但查询的结果从来都不一样,事实上查询很难理解。

出于短期和长期原因的最佳方式是控制查询,随时随地使用本机查询。只需按照 DBA 的建议编写 Query 并将其作为 JPQL NamedNativeQuery 放入,每个人都很高兴。享受!

于 2019-01-15T04:15:55.777 回答
-2

简而言之 - 你没有。几乎没有足够好的理由来调整 Hibernate 生成的查询,而且我非常怀疑在不修改源代码的情况下是否有可能。

我能想到的唯一简化是调用getResultList().get(0)而不是getSingleResult(). 它会稍微简化生成的 SQL,但会导致性能下降,而不是改进,因为您将从数据库中获取所有匹配的行。

但是,您可以稍微改进一下查询。

您知道要查询的类的简单名称。无需先从实体中获取并连接,只需使用

SELECT a FROM Person a...

连接查询的参数不是一个好习惯。它使您的查询容易受到 SQL 注入攻击。最好写:

...WHERE lower(a.firstName) = :firstName");
query.setParameter("firstName", firstName);
于 2013-03-15T10:17:01.133 回答