Автор оригинала: Vlad Mihalcea.
Теперь, когда я рассмотрел Гибернацию пакетную поддержку ВСТАВКИ, ОБНОВЛЕНИЯ и УДАЛЕНИЯ операторов, пришло время проанализировать ВЫБОР пакетную выборку набора результатов операторов.
JDBC Набор результатов предлагает курсор на стороне клиента Прокси для извлечения данных возврата текущего оператора. Когда инструкция выполняется, результат должен быть перенесен с курсора базы данных на курсор на стороне клиента. Эту операцию можно выполнить либо сразу, либо по требованию.
Существует три типа Результирующего набора курсоров :
TYPE_FORWARD_ ТОЛЬКО | Это тип курсора результирующего набора по умолчанию. Результирующий набор можно перемещать только вперед, и полученные данные могут быть либо извлечены сразу, либо извлечены во время итерации курсора. База данных может принять решение о извлечении данных в том виде, в каком они были доступны во время запуска запроса, или в том виде, в каком они есть при извлечении. |
TYPE_SCROLL_ЧУВСТВИТЕЛЕН | Результирующий набор можно прокручивать как вперед, так и назад, и полученные данные нечувствительны к одновременным изменениям, происходящим, пока курсор все еще открыт |
TYPE_SCROLL_ЧУВСТВИТЕЛЬНЫЙ | Результирующий набор можно прокручивать как вперед, так и назад, и полученные данные чувствительны к одновременным изменениям, происходящим, пока курсор все еще открыт. Таким образом, данные извлекаются по требованию, а не извлекаются из кэша курсоров базы данных |
Не все драйверы баз данных реализуют все типы курсоров, и поведение пакетной выборки контролируется с помощью оператора JDBC FetchSize свойства, которое в соответствии с Javadoc :
Дает драйверу JDBC подсказку о количестве строк, которые должны быть извлечены из базы данных, когда требуется больше строк для набора результатов объектов, созданных этим Оператором . Если указанное значение равно нулю, то подсказка игнорируется. Значение по умолчанию равно нулю.
Таким образом, стратегия выборки по умолчанию зависит от конкретной базы данных, и с точки зрения производительности приложения этот аспект очень важен при настройке уровня доступа к данным:
По умолчанию, когда Oracle JDBC выполняет запрос, он извлекает результирующий набор из 10 строк одновременно из курсора базы данных.
Согласно драйверу Oracle JDBC документации :
- MySQL
- SQL Server
- PostgreSQL
- DB2
Интерфейс Сохраняемость Java |/Запрос предлагает только получение полного результата с помощью вызова метода Query.getResultList () . Hibernate также поддерживает прокручиваемые Результирующие курсоры с помощью своего специального Query.scroll() API.
Единственным очевидным преимуществом прокручиваемых наборов результатов является то, что мы можем избежать проблем с памятью на стороне клиента, поскольку данные извлекаются по требованию. Это может показаться естественным выбором, но на самом деле вы не должны получать большие наборы результатов по следующим причинам:
- Большие результирующие наборы требуют значительных ресурсов сервера баз данных , и поскольку база данных является очень параллельной средой , это может затруднить доступность и масштабируемость
- Таблицы, как правило, увеличиваются в размерах, и умеренный набор результатов может легко превратиться в очень большой. Подобная ситуация возникает в производственных системах спустя долгое время после отправки кода приложения. Поскольку пользователи могут просматривать только относительно небольшую часть всего набора результатов, разбиение на страницы является более масштабируемой альтернативой извлечения данных
- Чрезмерно распространенный смещение подкачка не подходит для больших наборов результатов (поскольку время отклика линейно увеличивается с номером страницы), и вам следует учитывать набор ключей разбиение на страницы при просмотре больших наборов результатов. Набор ключей разбиение на страницы обеспечивает постоянное время отклика без учета относительного положения извлекаемой страницы
- Даже для заданий пакетной обработки всегда безопаснее ограничивать элементы обработки умеренным размером пакета. Большие пакеты могут привести к проблемам с памятью или вызвать длительные транзакции, которые увеличивают размер журнала транзакций отмены/повтора
Наша модель доменных сущностей выглядит следующим образом:
Следующий тест будет использоваться для проверки различных способов извлечения набора результатов:
@Test public void testFetchSize() { doInTransaction(session -> { int batchSize = batchSize(); for(int i = 0; i < itemsCount(); i++) { Post post = new Post(String.format( "Post no. %d", i)); int j = 0; post.addComment(new Comment( String.format( "Post comment %d:%d", i, j++))); post.addComment(new Comment( String.format( "Post comment %d:%d", i, j++))); session.persist(post); if(i % batchSize == 0 && i > 0) { session.flush(); session.clear(); } } }); long startNanos = System.nanoTime(); LOGGER.info("Test fetch size"); doInTransaction(session -> { List posts = session.createQuery( "select p " + "from Post p " + "join fetch p.comments ") .list(); LOGGER.info("{}.fetched {} entities", getClass().getSimpleName(), posts.size()); }); LOGGER.info("{}.testFetch took {} millis", getClass().getSimpleName(), TimeUnit.NANOSECONDS.toMillis( System.nanoTime() - startNanos )); }
Чтобы настроить Hibernate для использования явного оператора размер выборки , нам нужно задать следующее свойство Hibernate :
properties.put("hibernate.jdbc.fetch_size", fetchSize());
Каждый тест будет вставлять 5000 сообщений сущностей, каждая из которых имеет 2 комментария .
Одна коммерческая база данных
Первые тесты выполняются с использованием коммерческой базы данных со следующими результатами:
1 | 1190 |
10 | 640 |
100 | 481 |
1000 | 459 |
10000 | 449 |
По умолчанию (10) | 545 |
Чем больше размер выборки, тем меньше обходов требуется для извлечения всего набора результатов. Если возвращаемые строки содержат много столбцов, больший размер выборки потребует пропорционально больших буферов базы данных.
PostgreSQL
Второй раунд тестирования выполняется в PostgreSQL 9.4 со следующими результатами:
1 | 1181 |
10 | 572 |
100 | 485 |
1000 | 458 |
10000 | 437 |
По умолчанию (все) | 396 |
Размер выборки по умолчанию дает наилучший результат, даже если размер выборки равен общему количеству возвращаемых строк. Поскольку нет верхнего предела буфера, размер выборки по умолчанию может вызвать Ошибку при извлечении больших наборов результатов.
Хотя большинство серверов баз данных не устанавливают верхний предел по умолчанию для размера выборки результирующего набора, рекомендуется ограничить весь результирующий набор (если это позволяют требования). Набор результатов ограниченного размера должен устранять недостаток неограниченного размера выборки, обеспечивая при этом предсказуемое время отклика, даже когда запрашиваемые данные постепенно растут. Чем короче запросы, тем быстрее снимаются блокировки на уровне строк и тем более масштабируемым становится уровень доступа к данным.
Код доступен на GitHub .