休眠可以用于对性能敏感的应用程序吗?

2022-09-02 04:17:23

我看到检索与其他对象具有许多关系的对象的多个实例时出现性能问题。我正在使用Spring和Hibernate的JPA实现与MySQL。问题是,在执行 JPA 查询时,Hibernate 不会自动联接到其他表。这将导致 n*r + 1 个 SQL 查询,其中 n 是要检索的对象数,r 是关系数。

例如,一个人住在一个地址,有许多爱好,并且去过许多国家:

@Entity
public class Person {
    @Id public Integer personId;    
    public String name;    
    @ManyToOne public Address address;    
    @ManyToMany public Set<Hobby> hobbies;    
    @ManyToMany public Set<Country> countriesVisited;
}

当我执行 JPA 查询以获取所有名为 Bob 的人员时,数据库中有 100 个 Bob:

SELECT p FROM Person p WHERE p.name='Bob'

Hibernate 将其转换为 301 个 SQL 查询:

SELECT ... FROM Person WHERE name='Bob'
SELECT ... FROM Address WHERE personId=1
SELECT ... FROM Address WHERE personId=2
...
SELECT ... FROM Hobby WHERE personId=1
SELECT ... FROM Hobby WHERE personId=2
...
SELECT ... FROM Country WHERE personId=1
SELECT ... FROM Country WHERE personId=2
...

根据休眠常见问题解答(此处此处),解决方案是在查询中指定 LEFT JOIN 或 LEFT OUTER JOIN(对于多对多)。所以现在我的查询看起来像这样:

SELECT p, a, h, c FROM Person p
LEFT JOIN p.address a LEFT OUTER JOIN p.hobbies h LEFT OUTER JOIN p.countriesVisited c
WHERE p.name = 'Bob'

这有效,但如果存在多个 LEFT OUTER JOIN,则似乎存在错误,在这种情况下,Hibernate 错误地查找不存在的列:

could not read column value from result set: personId69_2_; Column 'personId69_2_' not found.

该错误行为似乎可能已通过 Hibernate 核心错误 HHH-3636 解决。不幸的是,该修复程序不是任何已发布的Hibernate JAR的一部分。我已经针对快照版本运行了我的应用程序,但错误行为仍然存在。我还从存储库中的最新代码构建了自己的Hibernate Core JAR,并且错误行为仍然存在。因此,也许HHH-3636没有解决这个问题。

这种休眠性能限制非常令人沮丧。如果我查询 1000 个对象,则对数据库进行 1000*r + 1 个 SQL 查询。在我的情况下,我有8个关系,所以我得到了8001个SQL查询,这导致了可怕的性能。官方的Hibernate解决方案是左加入所有关系。但是,由于错误行为,对于多个多对多关系,这是不可能的。因此,由于多对多关系,我坚持使用左联接进行多对一关系和 n*r+1 查询。我计划将 LEFT OUTER JOIN 问题作为休眠错误提交,但与此同时,我的客户需要一个具有合理性能的应用。我目前使用批处理提取(BatchSize),ehcache和自定义内存中缓存的组合,但性能仍然很差(它改进了从30秒到8秒的5000个对象的检索)。最重要的是,有太多的 SQL 查询命中数据库。

那么,我的问题是,是否可以在性能敏感型应用程序中使用Hibernate,其中表彼此具有多个关系?我很想听听Hibernate如何成功地使用地址性能。我应该手写SQL(这有点违背了使用Hibernate的目的)吗?是否应取消规范化数据库架构以减少联接表的数量?如果我需要快速的查询性能,我不应该使用Hibernate吗?有没有更快的东西?


答案 1

请参阅我对您的另一个问题的答案,如果您阅读了您链接到的整个常见问题解答:

遵循最佳实践指南!确保所有和映射在Hibernate2中指定lazy=“true”(这是Hibernate3中的新默认值)。使用 HQL LEFT JOIN FETCH 指定需要在初始 SQL SELECT 中检索哪些关联。

避免 n+1 选择问题的第二种方法是在 Hibernate3 中使用 fetch=“subselect”。

如果您仍然不确定,请参阅休眠文档和休眠在行动。

请参阅有关提高性能的提示。如果你不小心连接,你最终会得到笛卡尔积问题。


答案 2

除了“fetch”策略之外,您还可以尝试在休眠属性中设置批量提取大小,这样它就不会逐个运行联接查询,而是分批运行。

在您的 appContext 中.xml:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    ...    
    <property name="hibernateProperties">
        <props>        
            ...
            <prop key="hibernate.default_batch_fetch_size">32</prop>
        </props>
    </property>
</bean>

因此,而不是:

SELECT ... FROM Hobby WHERE personId=1
SELECT ... FROM Hobby WHERE personId=2

您将获得:

SELECT ... FROM Hobby WHERE personId in (1,2,...,32);
SELECT ... FROM Hobby WHERE personId in (33,34,...,64);

推荐