Можно ли использовать Hibernate в приложениях, чувствительных к производительности?
Я вижу проблемы с производительностью при получении нескольких экземпляров объектов, которые имеют много связей с другими объектами. Я использую 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, чтобы получить всех людей с именем Боб, и в базе данных есть 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 (здесь и здесь), решение состоит в том, чтобы указать 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 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, если мне нужна высокая производительность запросов? Есть ли что-то быстрее?
Ответы
Ответ 1
Посмотрите мой ответ на свой другой вопрос, если вы прочитали все часто задаваемые вопросы, на которые вы ссылались:
Следуйте руководству по лучшим практикам! Убедитесь, что все и сопоставления указывают lazy = "true" в Hibernate2 (это новое значение по умолчанию в Hibernate3). Используйте HQL LEFT JOIN FETCH, чтобы указать, какие ассоциации вам нужно получить в начальном SQL SELECT.
Второй способ избежать проблемы выбора n + 1 - использовать fetch = "subselect" в Hibernate3.
Если вы все еще не уверены, обратитесь к документации по Hibernate и Hibernate в действии.
Ознакомьтесь с советами по повышению производительности. Если вы не будете осторожны с объединениями, у вас возникнут проблемы с декартовым продуктом.
Ответ 2
Помимо стратегии "извлечения", вы также можете попытаться установить размер пакетного извлечения в свойствах гибернации, чтобы он выполнял объединение запросов не один за другим, а пакетами.
В вашем 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);
Ответ 3
Вы пробовали использовать стратегию получения для коллекций?
Ответ 4
Если вам нужна функция Hibernate, и эта функция глючит, у вас есть два варианта:
a) Отправьте запрос об ошибке и используйте обходной путь (низкая производительность или рукописный sql), пока ошибка не будет исправлена, что займет некоторое время
б) Отправить запрос об ошибке вместе с исправлением и тестами. (конечно, вы можете просто использовать исправление и пропустить запрос на исправление ошибок и тестовую часть).