Статьи

Производительность JPA, не игнорируйте базу данных

Хороший дизайн схемы базы данных важен для производительности. Одной из самых основных оптимизаций является создание таблиц, занимающих как можно меньше места на диске , это ускоряет чтение с диска и использует меньше памяти для обработки запросов.

Типы данных

Вы должны использовать наименьшие возможные типы данных , особенно для индексированных полей. Чем меньше ваши типы данных, тем больше индексов (и данных) может поместиться в блок памяти, тем быстрее будут выполняться ваши запросы.

нормализация

Нормализация базы данных устраняет избыточные данные, что, как правило, ускоряет обновления, поскольку данных для изменения меньше. Однако нормализованная схема вызывает соединения для запросов, что замедляет запросы, а денормализация ускоряет поиск. Более нормализованные схемы лучше подходят для приложений, в которых используется много транзакций, а менее нормализованные — лучше для отчетов о типах приложений. Вы должны сначала нормализовать свою схему, а затем отменить нормализацию. Приложениям часто приходится смешивать подходы, например, использовать частично нормализованную схему и дублировать или кэшировать выбранные столбцы из одной таблицы в другой таблице. С отображением JPA O / R вы можете использовать аннотацию @Embedded для денормализованных столбцов, чтобы указать постоянное поле, @Embeddable которого Тип может храниться как неотъемлемая часть объекта-владельца и совместно использовать идентификатор объекта.


Hierchies наследования базы данных и отображения

Иерархия наследования классов, показанная ниже, будет использоваться в качестве примера отображения JPA O / R.

В приведенном ниже сопоставлении «Одна таблица для каждого класса» все классы в иерархии сопоставляются с одной таблицей в базе данных . Эта таблица имеет столбец дискриминатора (отображаемый @DiscriminatorColumn) , который идентифицирует подкласс. Преимущества: это быстро для запросов, никаких соединений не требуется. Недостатки: растрата пространства , так как все унаследованных полей в каждой строке, глубокая иерархия наследования приведет широкие таблицы со многими, некоторыми пустыми колоннами .

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

В отображении «Таблица на класс» (в JPA 2.0, необязательно в JPA 1.0) каждый конкретный класс сопоставляется с таблицей в базе данных, и все унаследованные состояния повторяются в этой таблице. Это не нормализовано, унаследованные данные повторяются, что приводит к потере места. Запросы для сущностей одного типа выполняются быстро, однако полиморфные запросы вызывают объединения, которые работают медленнее.

Знать, какой SQL выполняется

Вы должны понимать запросы SQL, которые делает ваше приложение, и оценивать их производительность. Рекомендуется включить ведение журнала SQL, а затем пройти сценарий сценария использования, чтобы проверить выполненный SQL. Ведение журнала не является частью спецификации JPA. С EclipseLink вы можете включить ведение журнала SQL, установив следующее свойство в файле persistence.xml:

<properties>
<property name="eclipselink.logging.level" value="FINE"/>
</properties>

С Hibernate вы устанавливаете следующее свойство в файле persistence.xml:

 

<properties>
<property name="hibernate.show_sql" value="true" />
</properties>

По сути, вы хотите, чтобы ваши запросы обращались к меньшему количеству данных, извлекает ли ваше приложение больше данных, чем нужно, или запросы обращаются к слишком большому количеству строк или столбцов? Анализирует ли запрос к базе данных больше строк, чем нужно? Не упустите следующее:

  • запросы, которые выполняются слишком часто для получения необходимых данных
  • получение большего количества данных, чем необходимо
  • запросы, которые слишком медленные

    • Вы можете использовать EXPLAIN, чтобы увидеть, где вы должны добавить индексы

В MySQL вы можете использовать медленный журнал запросов, чтобы увидеть, какие запросы выполняются медленно, или вы можете использовать анализатор запросов MySQL, чтобы увидеть медленные запросы, количество выполнений запросов и результаты операторов EXPLAIN.

Понимание EXPLAIN

Для медленных запросов вы можете поставить перед оператором SELECT ключевое слово EXPLAIN,   чтобы получить информацию о плане выполнения запроса, который объясняет, как он будет обрабатывать SELECT, включая информацию о том, как таблицы объединяются и в каком порядке. Это помогает найти недостающие индексы на ранних стадиях процесса разработки.

Вы должны индексировать столбцы, которые часто используются в выражениях Query WHERE, GROUP BY, и столбцы, часто используемые в соединениях, но имейте в виду, что индексы могут замедлять вставки и обновления.

Ленивая загрузка и JPA

При использовании JPA отношения «многие к одному» и «многие ко многим» по умолчанию лениво загружаются, что означает, что они будут загружены при доступе к объекту в отношении. Ленивая загрузка обычно хороша, но если вам нужен доступ ко всем «многим» объектам в отношениях, это приведет к тому, что n + 1 выберет, где n — количество «многих» объектов.

Вы можете изменить загружаемую связь следующим образом:

Однако вы должны быть осторожны с энергичной загрузкой, которая может вызвать операторы SELECT, которые извлекают слишком много данных. Это может вызвать декартово произведение, если вы с готовностью загружаете сущности несколькими связанными коллекциями.

Если вы хотите переопределить тип извлечения LAZY для конкретных случаев использования, вы можете использовать Fetch Join. Например, этот запрос будет охотно загружать адреса сотрудников:

В общем, вы должны лениво загружать отношения, тестировать сценарии использования, проверять журнал SQL и использовать @NameQueries с JOIN FETCH для активной загрузки при необходимости.

Разметка

Основная цель секционирования состоит в том, чтобы уменьшить объем данных, считываемых для определенных операций SQL, так что общее время отклика сокращается.

Вертикальное разбиение   разбивает таблицы с множеством столбцов на несколько таблиц с меньшим количеством столбцов, так что в определенный набор данных включаются только определенные столбцы, причем каждый раздел включает все строки.

Горизонтальное разбиение сегментирует строки таблицы так, что формируются отдельные группы физических наборов данных на основе строк. Все столбцы, определенные для таблицы, находятся в каждом наборе разделов. Примером горизонтального разделения может быть таблица, которая содержит исторические данные, разбитые по дате.

Вертикальное разделение

В примере вертикального разбиения под таблицей, которая содержит ряд очень широких текстовых или BLOB-столбцов, на которые часто не ссылаются, разбивается на две таблицы, в которых столбцы с наибольшим количеством ссылок в одной таблице и редко встречающиеся текстовые или BLOB-столбцы в другой ,

Удалив большие столбцы данных из таблицы, вы получите более быстрое время ответа на запрос для наиболее часто используемых данных клиента. Широкие таблицы могут замедлять запросы, поэтому вы всегда должны убедиться, что все столбцы, определенные для таблицы, действительно необходимы.

В приведенном ниже примере показано отображение JPA для таблиц выше. Таблица данных Customer с более часто используемыми и более мелкими типами данных сопоставляется с сущностью Customer, таблица CustomerInfo с менее часто используемыми и более крупными типами данных сопоставляется с сущностью CustomerInfo с лениво загруженным отношением один к одному с Customer.

Горизонтальное разбиение

Основными формами горизонтального разделения являются Range, Hash, Hash Key, List и Composite.

Горизонтальное разбиение может сделать запросы быстрее, потому что оптимизатор запросов знает, какие разделы содержат данные, которые будут удовлетворять конкретному запросу, и будут получать доступ только к тем необходимым разделам во время выполнения запроса. Горизонтальное разбиение лучше всего подходит для больших баз данных. Приложения, которые содержат много запросов, нацеленных на определенные диапазоны таблиц базы данных.

Осколки гибернации

Разбиение данных по горизонтали на «осколки» используется Google , linkedin и другими для обеспечения максимальной масштабируемости для очень больших объемов данных. eBay «разделяет» данные горизонтально по основному пути доступа.

Hibernate Shards — это фреймворк, предназначенный для инкапсуляции поддержки горизонтального разбиения в Hibernate Core.

Кэширование

Кеширование JPA уровня 2 позволяет избежать доступа к базе данных для уже загруженных объектов, что ускоряет чтение часто используемых неизмененных объектов, однако может привести к плохой масштабируемости для частых или одновременно обновляемых объектов.

Вы должны настроить кэширование L2 для объектов, которые:

  • читаю часто
  • изменено нечасто
  • Не критично, если несвежий

Вам также следует настроить кэширование L2 (зависит от поставщика) для maxElements, время истечения, обновление …

Ссылки и дополнительная информация:

Презентация JPA Best Practices

Статья

MySQL для разработчиков Презентация

MySQL для разработчиков Скринкаст MySQL для разработчиков

Сохранение реляционной перспективы для оптимизации персистентности

Java Сохранение Java с помощью Hibernate

Pro EJB 3: API

персистентности Java API
персистентности Java 2.0: что нового?

Высокопроизводительная книга MySQL

Pro MySQL, Глава 6: Сравнительный анализ и профилирование

EJB 3 в действии,

разделяющий гибернационный способ.

Советы по кешированию JPA

для крупномасштабных веб-сайтов: уроки от eBay