我发现检索与其他对象有很多关系的多个对象实例时出现性能问题.我在MySQL中使用Spring和Hibernate的JPA实现.问题是,在执行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 Sethobbies; @ManyToMany public Set countriesVisited; }
当我执行JPA查询以获取名为Bob的所有人员时,数据库中有100个Bobs:
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 ...
根据Hibernate FAQ(此处和此处),解决方案是在查询中指定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 Core bug HHH-3636可能会解决bug行为.不幸的是,修复程序不是任何已发布的Hibernate JAR的一部分.我已经针对快照构建运行了我的应用程序,但bug行为仍然存在.我还从存储库中的最新代码构建了自己的Hibernate Core JAR,并且仍然存在bug行为.所以也许HHH-3636没有解决这个问题.
这种Hibernate性能限制非常令人沮丧.如果我查询1000个对象,则对数据库进行1000*r + 1个SQL查询.在我的情况下,我有8个关系,所以我得到8001 SQL查询,这导致可怕的性能.官方的Hibernate解决方案就是将所有关系都加入.但由于bug行为导致多个多对多关系不可能实现这一点.因此,由于多对多关系,我因左对联而陷入多对一关系和n*r + 1查询.我计划将LEFT OUTER JOIN问题作为Hibernate错误提交,但与此同时我的客户需要一个具有合理性能的应用程序.我目前使用批量提取(BatchSize)的组合,ehcache和自定义内存缓存,但性能仍然很差(它改进了从30到8秒检索5000个对象).底线是太多的SQL查询正在访问数据库.
那么,我的问题是,是否可以在性能敏感的应用程序中使用Hibernate,其中表之间存在多种关系?我很想知道Hibernate如何成功使用地址性能.我应该亲自编写SQL(这有点违背了使用Hibernate的目的)?我应该取消规范化我的数据库架构以减少连接表的数量吗?如果我需要快速查询性能,我应该不使用Hibernate吗?有更快的东西吗?
如果您阅读了链接到的所有常见问题解答,请参阅我对您的其他问题的回答:
遵循最佳实践指南!确保all和mappings在Hibernate2中指定lazy ="true"(这是Hibernate3中的新默认值).使用HQL LEFT JOIN FETCH指定在初始SQL SELECT中需要检索哪些关联.
避免n + 1选择问题的第二种方法是在Hibernate3中使用fetch ="subselect".
如果您仍然不确定,请参阅Hibernate文档和Hibernate in Action.
请参阅有关提高性能的提示.如果您对连接不小心,最终会遇到笛卡尔积问题.
除了"获取"策略之外,您还可以尝试在hibernate属性中设置批量提取大小,因此它将逐个运行加入查询但是批量运行.
在appContext.xml中:
... ... 32
所以代替:
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);