2009-03-16 3 views
21

Я вижу проблемы с производительностью при извлечении нескольких экземпляров объектов, которые имеют много связей с другими объектами. Я использую реализацию JPA Spring и Hibernate с MySQL. Проблема в том, что при выполнении запроса JPA Hibernate автоматически не присоединяется к другим таблицам. Это приводит к n * r + 1 SQL-запросам, где n - количество возвращаемых объектов, а r - количество отношений.Может ли Hibernate использоваться в приложениях с высокой чувствительностью?

Например, человек живет по адресу, имеет много увлечений, и побывал во многих странах:

@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, чтобы получить все лица, поименованные Боб, и есть 100 качается в база данных:

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 (here и here), решение должно указать LEFT JOIN или LEFT OUTER JOIN (for many-to-many) в запросе. Так что теперь мой запрос выглядит следующим образом:

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. К сожалению, исправление не является частью выпущенного Hibernate JAR. Я выполнил свое приложение против сборки моментальных снимков, но поведение ошибки все еще присутствует. Я также создал собственный Hibernate Core JAR из последнего кода в репозитории, и поведение ошибки все еще присутствует. Поэтому, возможно, HHH-3636 не решает этого.

Это ограничение производительности Hibernate очень расстраивает. Если я запрашиваю 1000 объектов, то в базу данных поступает 1000 * r + 1 SQL-запросов. В моем случае у меня 8 отношений, поэтому я получаю 8001 SQL-запросы, что приводит к ужасной производительности. Официальное решение Hibernate для этого состоит в том, чтобы оставить все отношения. Но это невозможно с более чем одним отношением «многие ко многим» из-за поведения ошибки. Таким образом, я застрял с левыми объединениями для отношений «один к одному» и n * r + 1 запросов из-за отношений «многие ко многим». Я планирую представить проблему LEFT OUTER JOIN как ошибку Hibernate, но в то же время моему клиенту нужно приложение, которое имеет разумную производительность. В настоящее время я использую комбинацию пакетной выборки (BatchSize), ehcache и пользовательского кэширования в памяти, но производительность по-прежнему довольно плохая (улучшено извлечение 5000 объектов с 30 до 8 секунд). Суть в том, что слишком много запросов SQL попадают в базу данных.

Итак, мои вопросы, можно ли использовать Hibernate в приложениях с высокой чувствительностью, где таблицы имеют несколько взаимосвязей друг с другом? Мне бы хотелось услышать, как успешный Hibernate использует производительность адреса. Должен ли я вручную писать SQL (что несколько поражает цель использования Hibernate)? Должен ли я де-нормализовать мою схему базы данных, чтобы уменьшить количество объединенных таблиц? Должен ли я использовать Hibernate, если мне нужна быстрая производительность запросов? Есть что-то быстрее?

+0

Мы выпустили Batoo JPA, что составляет ~ 15 раз быстрее, Hibernate и реализует JPA Spec% 100. Основная мотивация проекта заключалась в том, что все три реализации JPA просто замедляются. Поняв, что JPA может быть намного быстрее, мы разработали Batoo JPA. Дайте ему попробовать http://batoo.jp –

ответ

11

Смотрите мой ответ на ваш other question, если вы читали весь FAQ вы связаны с:

Следуйте рекомендациям руководства! Убедитесь, что все и отображения указывают lazy = "true" в Hibernate2 (это новый по умолчанию в Hibernate3). Используйте HQL LEFT JOIN FETCH, чтобы указать, какие ассоциации вам нужно получить в исходном SQL SELECT.

Второй способ избежать п + 1 выбирает проблемы является использование выборки = «подвыбор» в Hibernate3.

Если вы все еще не уверены, обратитесь к документации Hibernate и Hibernate в действии.

См. Советы по improving performance. Если вы не будете осторожны с присоединениями, вы столкнетесь с проблемами Cartesian Product.

+0

Спасибо, я изучаю это прямо сейчас –

+0

Subselect отлично работает, спасибо. –

+0

Просто для уточнения настроек по умолчанию. Если вы используете аннотации, то Hibernate следует за настройкой по умолчанию, указанной спецификацией JPA, которая заявляет, что по умолчанию все-к-одному и взаимно-однозначные ассоциации нетерпеливы, а коллекции по умолчанию являются ленивыми. –

0

Если вам нужна особенность Hibernate, и эта функция глючит у вас есть два варианта: а) оставившего bugrequest и используйте обходной путь (низкую производительность или рукописный SQL), пока ошибка не будет устранена, который займет некоторое время б) Отправьте bugrequest вместе с исправлением и тестами. (конечно, вы можете просто использовать исправление и пропустить часть bugrequest и test).

7

Помимо стратегии «выборки» вы также можете попробовать установить размер выборки партии в свойствах спящего режима, поэтому он будет запускать запросы не по одному, а по партиям.

В вашем 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); 
Смежные вопросы