16 Вопрос: Что такое «проблема выбора N + 1» в ORM (объектно-реляционное отображение)?

вопрос создан в Tue, Feb 26, 2019 12:00 AM

«N + 1 выбирает проблему» обычно указывается как проблема в обсуждениях объектно-реляционного отображения (ORM), и я понимаю, что это связано с необходимостью выполнять множество запросов к базе данных для чего-то, что кажется простым в объектном мире.

У кого-нибудь есть более подробное объяснение проблемы?

    
1453
  1. ИМО javalobby.org/java /forums /t20533.html это объяснение лучше
    2012-06-19 05: 48: 00Z
  2. Это отличная ссылка с хорошим объяснением понимания проблемы n + 1 . В нем также рассматриваются решения для борьбы с этой проблемой: architects.dzone.com/статьи /как идентифицировать, и-resilve-n1
    2012-08-29 20: 29: 23Z
  3. 2015-10-16 10: 08: 36Z
  4. Для всех, кто ищет решение этой проблемы, я нашел пост, описывающий ее. stackoverflow.com/questions/32453989/…
    2018-12-10 00: 30: 00Z
  5. Учитывая ответы, не следует ли это назвать проблемой 1 + N? Поскольку это, кажется, терминология, я, в частности, не спрашиваю OP.
    2019-02-04 15: 09: 00Z
16 ответов                              16                         

Допустим, у вас есть коллекция объектов Car (строки базы данных), и каждый Car имеет коллекцию объектов Wheel (также строк). Другими словами, Car - > Wheel - это отношение «один ко многим».

Теперь предположим, что вам нужно пройтись по всем машинам, и для каждого из них распечатать список колес. Наивная реализация O /R сделала бы следующее:

SELECT * FROM Cars;

А затем для каждого Car:

SELECT * FROM Wheel WHERE CarId = ?

Другими словами, у вас есть один выбор для автомобилей, а затем N дополнительных выборов, где N - общее количество автомобилей.

Кроме того, можно получить все колеса и выполнить поиск в памяти:

SELECT * FROM Wheel

Это уменьшает количество обращений к базе данных с N + 1 до 2. Большинство инструментов ORM дают вам несколько способов предотвратить выбор N + 1.

Ссылка: Сохранение Java с Hibernate , глава 13. р>     

890
2017-11-21 09: 09: 43Z
  1. Чтобы уточнить "Это плохо" - вы можете получить все колеса с 1 выбором (SELECT * from Wheel;) вместо N + 1. При большом N снижение производительности может быть очень значительным.
    2010-08-30 10: 43: 07Z
  2. @ tucuxi Я удивлен, что вы получили так много голосов за то, что ошиблись. База данных очень хороша в отношении индексов, выполнение запроса для определенного CarID будет очень быстрым. Но если вы получили все колеса один раз, вам придется искать CarID в вашем приложении, которое не индексируется, это медленнее. Если у вас нет серьезных задержек при достижении вашей базы данных, n + 1 на самом деле быстрее - и да, я сравнил его с большим разнообразием реального кода.
    2011-10-30 20: 32: 29Z
  3. @ ariel «Правильный» способ - получить все колеса, упорядоченные CarId (1 выбор), и, если больше деталей, чем Требуется CarId, сделайте второй запрос для всех автомобилей (всего 2 запроса). Распечатка данных теперь является оптимальной, и не требуется никаких индексов или вторичного хранилища (вы можете перебирать результаты, не загружая их все). Вы отметили не то. Если вы все еще уверены в своих контрольных показателях, не могли бы вы опубликовать более длинный комментарий (или полный ответ), объясняющий ваш эксперимент и результаты?
    2011-11-01 12: 36: 39Z
  4. "Hibernate (я не знаком с другими платформами ORM) дает вам несколько способов справиться с этим." а эти способы есть?
    2012-01-12 14: 17: 26Z
  5. @ Ariel Попробуйте запустить свои тесты с серверами баз данных и приложений на разных компьютерах. По моему опыту, поездки в базу данных обходятся дороже, чем сам запрос. Так что да, запросы действительно быстрые, но это кругосветные путешествия, которые наносят ущерб. Я преобразовал "WHERE Id = const " в "WHERE Id IN ( const , const , ...)" и получил порядки величина увеличивается из-за этого.
    2012-10-02 23: 07: 26Z
SELECT 
table1.*
, table2.*
INNER JOIN table2 ON table2.SomeFkId = table1.SomeId

Это дает вам набор результатов, где дочерние строки в таблице2 вызывают дублирование, возвращая результаты таблицы1 для каждой дочерней строки в таблице2. Сопоставители O /R должны дифференцировать экземпляры table1 на основе уникального ключевого поля, а затем использовать все столбцы table2 для заполнения дочерних экземпляров.

SELECT table1.*

SELECT table2.* WHERE SomeFkId = #

N + 1 - это место, где первый запрос заполняет первичный объект, а второй - все дочерние объекты для каждого из возвращенных уникальных первичных объектов.

Рассмотрим:

class House
{
    int Id { get; set; }
    string Address { get; set; }
    Person[] Inhabitants { get; set; }
}

class Person
{
    string Name { get; set; }
    int HouseId { get; set; }
}

и таблицы с похожей структурой. Один запрос по адресу "22 Valley St" может вернуть:

Id Address      Name HouseId
1  22 Valley St Dave 1
1  22 Valley St John 1
1  22 Valley St Mike 1

O /RM должен заполнить экземпляр Home с ID = 1, Address = "22 Valley St", а затем заполнить массив Inhabitants экземплярами People для Dave, John и Mike одним запросом.

Запрос N + 1 для того же адреса, который использовался выше, приведет к:

Id Address
1  22 Valley St

с отдельным запросом вроде

SELECT * FROM Person WHERE HouseId = 1

и приводит к отдельному набору данных, например

Name    HouseId
Dave    1
John    1
Mike    1

и окончательный результат такой же, как указано выше для одного запроса.

Преимущества одиночного выбора в том, что вы получаете все данные заранее, что может быть именно тем, что вы в конечном итоге желаете. Преимущество N + 1 в том, что сложность запроса снижается, и вы можете использовать отложенную загрузку, когда дочерние наборы результатов загружаются только при первом запросе.

    
104
2008-09-18 21: 43: 11Z
  1. Другое преимущество n + 1 состоит в том, что это быстрее, потому что база данных может возвращать результаты непосредственно из индекса. Выполнение объединения и последующей сортировки требует временной таблицы, которая медленнее. Единственная причина, по которой следует избегать n + 1, это если у вас много задержек при обращении к вашей базе данных.
    2011-10-30 20: 34: 07Z
  2. Объединение и сортировка могут быть довольно быстрыми (потому что вы будете объединяться в индексированных и, возможно, отсортированных полях). Насколько велика ваша 'n + 1'? Вы всерьез полагаете, что проблема n + 1 относится только к соединениям с базами данных с высокой задержкой?
    2011-11-01 12: 46: 39Z
  3. @ ariel - Ваш совет, что N + 1 является "самым быстрым", неверен, дажехотя ваши критерии могут быть правильными. Как это возможно? См. en.wikipedia.org/wiki/Anecdotal_evidence , а также мой комментарий в другом ответе на этот вопрос.
    2012-07-04 18: 27: 31Z
  4. @ Ариэль - мне кажется, я все понял :). Я просто пытаюсь указать, что ваш результат относится только к одному набору условий. Я мог бы легко построить контрпример, который показал обратное. Это имеет смысл?
    2012-07-05 00: 31: 57Z
  5. Повторюсь, проблема SELECT N + 1, по своей сути: у меня есть 600 записей для извлечения. Быстрее ли получить все 600 из них за один запрос или по одному за 600 запросов? Если вы не используете MyISAM и /или у вас плохо нормализованная /плохо проиндексированная схема (в этом случае ORM не проблема), правильно настроенная БД вернет 600 строк за 2 мс, а отдельные строки вернет около 1 мс каждый. Таким образом, мы часто видим, что N + 1 занимает сотни миллисекунд, а соединение занимает всего пару
    2016-08-25 02: 54: 02Z

Поставщик, имеющий отношения один-ко-многим с продуктом. Один поставщик имеет (поставляет) много товаров.

***** Table: Supplier *****
+-----+-------------------+
| ID  |       NAME        |
+-----+-------------------+
|  1  |  Supplier Name 1  |
|  2  |  Supplier Name 2  |
|  3  |  Supplier Name 3  |
|  4  |  Supplier Name 4  |
+-----+-------------------+

***** Table: Product *****
+-----+-----------+--------------------+-------+------------+
| ID  |   NAME    |     DESCRIPTION    | PRICE | SUPPLIERID |
+-----+-----------+--------------------+-------+------------+
|1    | Product 1 | Name for Product 1 |  2.0  |     1      |
|2    | Product 2 | Name for Product 2 | 22.0  |     1      |
|3    | Product 3 | Name for Product 3 | 30.0  |     2      |
|4    | Product 4 | Name for Product 4 |  7.0  |     3      |
+-----+-----------+--------------------+-------+------------+

Факторы:

  • Ленивый режим для поставщика установлен в «true» (по умолчанию)

  • Режим выборки, используемый для запроса по продукту, - Выбрать

  • Режим извлечения (по умолчанию): доступ к информации о поставщике

  • Кэширование не играет роли в первый раз, когда

  • Доступ к поставщику

Режим извлечения - «Выбрать извлечение» (по умолчанию)

// It takes Select fetch mode as a default
Query query = session.createQuery( "from Product p");
List list = query.list();
// Supplier is being accessed
displayProductsListWithSupplierName(results);

select ... various field names ... from PRODUCT
select ... various field names ... from SUPPLIER where SUPPLIER.id=?
select ... various field names ... from SUPPLIER where SUPPLIER.id=?
select ... various field names ... from SUPPLIER where SUPPLIER.id=?

Результат:

  • 1 выбор оператора для продукта
  • N выберите заявления для поставщика

Это проблема выбора N + 1!

    
61
2014-07-04 15: 01: 23Z
  1. Предполагается, что это 1 выбор для поставщика, а N выбор для продукта?
    2018-05-31 13: 16: 56Z
  2. @ bencampbell_ Да, изначально я чувствовал то же самое. Но с его примера это один продукт для многих поставщиков.
    2018-12-15 12: 23: 26Z

Я не могу комментировать другие ответы напрямую, потому что мне не хватает репутации. Но стоит отметить, что проблема, по сути, возникает только потому, что исторически, многие dbms были довольно плохими, когда дело доходит до обработки соединений (MySQL является особенно заслуживающим внимания примером). Таким образом, n + 1 часто был значительно быстрее, чем объединение. И затем есть способы улучшить n + 1, но все еще без необходимости объединения, к чему относится исходная проблема.

Однако MySQL теперь намного лучше, чем раньше, когда дело доходит до объединений. Когда я впервые изучил MySQL, я часто использовал соединения. Затем я обнаружил, насколько они медленные, и вместо этого переключился на n + 1 в коде. Но недавно я вернулся к объединениям, потому что теперь MySQL намного лучше справляется с ними, чем когда я впервые начал его использовать.

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

Это обсуждается здесь одним из разработчиков MySQL:

http://jorgenloland.blogspot.co.uk/2013/02/dbt-3-q3-6-x-performance-in-mysql-5610.html р>

Итак, подведем итоги: если в прошлом вы избегали объединений из-за ужасной производительности MySQL, попробуйте еще раз на последних версиях. Вы, вероятно, будете приятно удивлены.

    
36
2014-01-08 12: 49: 28Z
  1. Называть ранние версии MySQL реляционной СУБД довольно сложно ... Если бы люди, сталкивающиеся с этими проблемами, использовали реальную базу данных, они бы не столкнулись с такими видами проблемы. ; -)
    2016-04-02 23: 06: 58Z
  2. Интересно, что многие из этих типов проблем были решены в MySQL с введением и последующей оптимизацией механизма INNODB, но вы все равно столкнетесь с людьми, пытающимися продвигать MYISAM. потому что они думают, что это быстрее.
    2016-04-02 23: 08: 10Z
  3. FYI, один из 3 распространенных алгоритмов JOIN, используемых в СУБД, называется вложенными циклами. Это принципиально N + 1 выбор под капотом. Единственное отличие заключается в том, что БД сделала разумный выбор, чтобы использовать ее на основе статистики и индексов, а не кода клиента, принудительно применяя его по этому пути.
    2016-08-08 16: 50: 00Z
  4. @ Брэндон Да! Подобно подсказкам JOIN и INDEX, форсирование определенного пути выполнения во всех случаях редко побеждает базу данных. База данных почти всегда очень, очень хороша в выборе оптимального подхода для получения данных. Возможно, в первые дни существования базы данных вам нужно было «сформулировать» свой вопрос особым образом, чтобы убедить базу данных, но после десятилетий разработки мирового класса вы можете теперь добиться максимальной производительности, задавая своей базе данных реляционный вопрос и позволяя ей разберитесь, как получить и собрать эти данные для вас.
    2016-08-24 01: 52: 35Z
  5. Мало того, что база данных использует индексы и статистику, все операции также являются локальным вводом-выводом, большая часть которого часто работает с высокоэффективным кешем, а не с диском. Программисты баз данных уделяют огромное внимание оптимизации подобных вещей.
    2017-09-11 05: 37: 03Z

Из-за этой проблемы мы переехали из ORM в Джанго. В основном, если вы попытаетесь сделать

for p in person:
    print p.car.colour

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

Простой и очень эффективный подход к этому - это то, что я называю « фанфолдинг », что позволяет избежать бессмысленной идеи о том, что результаты запроса из реляционной базы данных должны отображаться обратно в исходные таблицы, из которых запрос состоит. р>

Шаг 1. Широкий выбор

  select * from people_car_colour; # this is a view or sql function

Это вернет что-то вроде

  p.id | p.name | p.telno | car.id | car.type | car.colour
  -----+--------+---------+--------+----------+-----------
  2    | jones  | 2145    | 77     | ford     | red
  2    | jones  | 2145    | 1012   | toyota   | blue
  16   | ashby  | 124     | 99     | bmw      | yellow

Шаг 2. Объективизация

Соси результаты в создатель универсального объекта с аргументом для разделения после третьего элемента. Это означает, что объект "jones" будет создан не более одного раза.

Шаг 3. Визуализация

for p in people:
    print p.car.colour # no more car queries

Смотрите эту веб-страницу , чтобы узнать о реализации фанфолдинга для питона.

    
26
2011-07-31 07: 49: 41Z
  1. Я так рад, что наткнулся на ваш пост, потому что я думал, что схожу с ума. Когда я узнал о проблеме N + 1, я сразу подумал: ну почему бы вам просто не создать представление, содержащее всю необходимую информацию, и извлечь из него представление? Вы подтвердили мою позицию. спасибо, сэр.
    2011-09-16 15: 07: 04Z
  2. Мы переехали из ORM в Django из-за этой проблемы. А? У Django есть select_related, который призван решить эту проблему - фактически, его документы начинаются с примера, подобного вашему примеру p.car.colour.
    2015-07-23 22: 08: 48Z
  3. Это старый ответ, сейчас у нас есть select_related() и prefetch_related() в Джанго.
    2016-08-12 06: 39: 21Z
  4. Круто. Но select_related() и его друг, кажется, не делают каких-либо явно полезных экстраполяций объединения, таких как LEFT OUTER JOIN. Проблема не в интерфейсе, а в странной идее, что объекты и реляционные данные сопоставимы .. ... на мой взгляд.
    2018-09-15 20: 45: 08Z

Предположим, у вас есть КОМПАНИЯ и СОТРУДНИК. У КОМПАНИИ много СОТРУДНИКОВ (т. Е. У СОТРУДНИКА есть поле COMPANY_ID).

В некоторых конфигурациях O /R, когда у вас есть сопоставленный объект Company и вы переходите к его объектам Employee, инструмент O /R будет делать один выбор для каждого сотрудника, тогда как если вы просто работали в прямом SQL, вы может select * from employees where company_id = XX. Таким образом, N (число сотрудников) плюс 1 (компания)

Так работали начальные версии EJB Entity Beans. Я считаю, что такие вещи, как Hibernate, покончили с этим, но я не слишком уверен. Большинство инструментов обычно содержат информацию о своей стратегии отображения.

    
17
2008-09-18 21: 33: 41Z

Вот хорошее описание проблемы - https://web.archive.org/web/20160310145416/http://www.realsolve.co.uk/site/tech/hib-tip -pitfall.php? имя = почему-ленивое

Теперь, когда вы понимаете проблему, ее обычно можно избежать, выполнив выборку соединения в вашем запросе. Это в основном вызывает выборку загруженного объекта с отложенным доступом, поэтому данные извлекаются в одном запросе вместо n + 1 запросов. Надеюсь, это поможет.

    
15
2019-03-01 11: 44: 25Z

По моему мнению, статья написана в Ловушка гибернации: почему отношения должны быть ленивыми - это полная противоположность реальной проблеме N + 1.

Если вам нужно правильное объяснение, пожалуйста, обратитесь Hibernate - Глава 19: Повышение производительности - Выбор стратегий

  

Выберите выборку (по умолчанию)   чрезвычайно уязвимы для N + 1 выбора   проблемы, поэтому мы могли бы включить   присоединиться к выборке

    
13
2010-08-26 11: 24: 59Z
  1. Я прочитал страницу гибернации. Это не говорит о том, что N + 1 выбирает проблему на самом деле является . Но он говорит, что вы можете использовать соединения, чтобы исправить это.
    2010-08-26 11: 25: 54Z
  2. размер пакета требуется для выборочной выборки, чтобы выбрать дочерние объекты для нескольких родителей в одном операторе select. Отбор может быть другой альтернативой. Объединения могут быть очень плохими, если у вас несколько уровней иерархии и создан декартово произведение.
    2013-10-03 20: 19: 38Z

Проверьте сообщение Айенде на тему: Борьба с проблемы выбора N + 1 в NHibernate

Как правило, при использовании ORM, такого как NHibernate или EntityFramework, если у вас есть отношение «один ко многим» (master-detail), и вы хотите перечислить все детали для каждой основной записи, вы должны сделать N + 1 запрос обращается к базе данных, где «N» - это число основных записей: 1 запрос, чтобы получить все основные записи, и N запросов, один для каждой основной записи, чтобы получить все сведения для основной записи.

Больше запросов к базе данныхзвонки - > больше времени ожидания - > снижение производительности приложения /базы данных.

Однако у ORM есть варианты, чтобы избежать этой проблемы, в основном используя "соединения".

    
13
2012-06-05 22: 21: 47Z
  1. объединения не являются хорошим решением (часто), потому что они могут привести к декартовому произведению, то есть число строк результата - это число результатов корневой таблицы, умноженное на количество результатов в каждой дочерней таблице. особенно плохо на множественных уровнях иерархии. Выбор 20 «блогов» со 100 «постами» в каждом и 10 «комментариями» в каждом посте приведет к 20000 строкам результатов. У NHibernate есть обходные пути, такие как «размер пакета» (выберите дочерние элементы с предложением in в родительских идентификаторах) или «отбор».
    2013-10-03 20: 12: 28Z

Проблема с запросом N + 1 возникает, когда вы забыли выбрать ассоциацию, а затем вам нужно получить к ней доступ:

List<PostComment> comments = entityManager.createQuery(
    "select pc " +
    "from PostComment pc " +
    "where pc.review = :review", PostComment.class)
.setParameter("review", review)
.getResultList();

LOGGER.info("Loaded {} comments", comments.size());

for(PostComment comment : comments) {
    LOGGER.info("The post title is '{}'", comment.getPost().getTitle());
}

Который генерирует следующие операторы SQL:

SELECT pc.id AS id1_1_, pc.post_id AS post_id3_1_, pc.review AS review2_1_
FROM   post_comment pc
WHERE  pc.review = 'Excellent!'

INFO - Loaded 3 comments

SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM   post pc
WHERE  pc.id = 1

INFO - The post title is 'Post nr. 1'

SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM   post pc
WHERE  pc.id = 2

INFO - The post title is 'Post nr. 2'

SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM   post pc
WHERE  pc.id = 3

INFO - The post title is 'Post nr. 3'

Сначала Hibernate выполняет запрос JPQL, и из него выбирается список PostComment.

Затем для каждого PostComment соответствующее свойство post используется для создания сообщения журнала, содержащего заголовок Post.

Поскольку связь post не инициализирована, Hibernate должен извлечь сущность Post с дополнительным запросом, и для N PostComment сущностей будет выполнено еще N запросов (следовательно, проблема с N + 1 запросами).

Во-первых, вам необходимо правильное ведение журнала SQL и мониторинг так что вы можете обнаружить эту проблему.

Во-вторых, такого рода проблемы лучше обнаруживать интеграционными тестами. Вы можете использовать автоматический запрос JUnit для проверить ожидаемое количество сгенерированных операторов SQL . проект db-unit уже предоставляет эту функциональность и является открытым исходным кодом.

Когда вы обнаружили проблему с запросом N + 1, вам нужно используйте JOIN FETCH, чтобы дочерние ассоциации выбирались в одном запросе, вместо N . Если вам нужно получить несколько дочерних ассоциаций, лучше выбрать одну коллекцию в начальном запросе, а вторую - вторичный SQL-запрос.

    
13
2018-01-04 18: 55: 06Z
  1. Но теперь у вас есть проблема с разбиением на страницы. Если у вас есть 10 автомобилей, каждая машина с 4 колесами, и вы хотите разбить автомобили на 5 машин на странице. Таким образом, вы в основном имеете SELECT cars, wheels FROM cars JOIN wheels LIMIT 0, 5. Но вы получаете 2 машины с 5 колесами (первый автомобиль со всеми 4 колесами и второй автомобиль только с 1 колесом), потому что LIMIT ограничит весь набор результатов, а не только корневое предложение.
    2017-12-05 20: 08: 19Z
  2. У меня есть статья для этого тоже.
    2017-12-05 22: 17: 38Z
  3. Спасибо за статью. Я прочитаю это. Благодаря быстрой прокрутке - я понял, что решение - это оконная функция, но они довольно новы в MariaDB - поэтому проблема сохраняется в более старых версиях. :)
    2017-12-05 23: 09: 08Z

Предоставленная ссылка имеет очень простой пример проблемы n + 1. Если вы примените его к Hibernate, то это в основном говорит об одном и том же. Когда вы запрашиваете объект, объект загружается, нолюбые ассоциации (если не указано иное) будут загружены с отложенной загрузкой. Отсюда один запрос для корневых объектов и другой запрос для загрузки ассоциаций для каждого из них. Возвращенные 100 объектов означают один начальный запрос, а затем 100 дополнительных запросов, чтобы получить ассоциацию для каждого, n + 1.

http://pramatr.com/2009/02 /05 /SQL-N-1-выбирает объясненном / р>     

10
2009-02-20 08: 33: 47Z

У одного миллионера есть N машин. Вы хотите получить все (4) колеса.

Один (1) запрос загружает все автомобили, но для каждого (N) автомобиля отправляется отдельный запрос на загрузку колес.

Расходы:

Предположим, что индексы вписываются в оперативную память.

Разбор и планирование 1 + N запросов + поиск по индексу И 1 + N + (N * 4) доступ к платформе для загрузки полезной нагрузки.

Предположим, что индексы не вписываются в оперативную память.

Дополнительные затраты в худшем случае для доступа к табличке 1 + N для индекса загрузки.

Резюме

Горлышко бутылки - доступ к пластине (около 70 раз в секунду при произвольном доступе на жестком диске) При активном выборе соединения также можно получить доступ к платформе 1 + N + (N * 4) раз для полезной нагрузки. Так что, если индексы вписываются в оперативную память - нет проблем, это достаточно быстро, потому что задействованы только оперативные памяти.     

9
2013-03-28 22: 32: 51Z

Гораздо быстрее выполнить 1 запрос, который возвращает 100 результатов, чем 100 запросов, каждый из которых возвращает 1 результат.

    
9
2014-11-07 10: 30: 07Z

N + 1 проблема выбора - это боль, и имеет смысл обнаруживать такие случаи в модульных тестах. Я разработал небольшую библиотеку для проверки количества запросов, выполненных заданным методом тестирования или просто произвольным блоком кода - JDBC Sniffer

Просто добавьте специальное правило JUnit к вашему тестовому классу и поместите аннотацию с ожидаемым количеством запросов в ваши тестовые методы:

@Rule
public final QueryCounter queryCounter = new QueryCounter();

@Expectation(atMost = 3)
@Test
public void testInvokingDatabase() {
    // your JDBC or JPA code
}
    
8
2015-04-15 07: 52: 59Z
  1. Эй, это круто! Я попробую это. Благодаря
    2016-06-30 02: 12: 26Z

Проблема, как говорили другие более элегантно, заключается в том, что у вас либо декартово произведение столбцов OneToMany, либо вы выполняете N + 1 выбор. Возможен либо гигантский набор результатов, либо общение с базой данных соответственно.

Я удивлен, что это не упоминается, но вот как я обошел эту проблему ... Я делаю таблицу временных кодов . Я также делаю это, когда у вас есть ограничение предложения IN () .

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

Сначала вы вставляете идентификаторы родительского объекта в виде пакета в таблицу идентификаторов. Этот batch_id - это то, что мы генерируем в нашем приложении и удерживаем.

INSERT INTO temp_ids 
    (product_id, batch_id)
    (SELECT p.product_id, ? 
    FROM product p ORDER BY p.product_id
    LIMIT ? OFFSET ?);

Теперь для каждого столбца OneToMany вы просто делаете SELECT для таблицы идентификаторов INNER JOIN, добавляя дочернюю таблицу в WHERE batch_id= (или наоборот). Вы просто хотите убедиться, что вы упорядочиваете по столбцу id, поскольку это упростит объединение столбцов результатов (в противном случае вам понадобится HashMap /Table для всего набора результатов, что может быть не так уж плохо).

Затем вы просто периодически очищаете таблицу идентификаторов.

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

Теперь количество запросов, которые вы выполняете, зависит от количества столбцов OneToMany.

    
5
2017-05-23 10: 31: 37Z

Возьмите пример Мэтта Солнита, представьте, что вы определяете связь между автомобилем и колесами как LAZY, и вам нужны некоторые поля колес. Это означает, что после первого выбора, hibernate будет делать «Выбрать * из колес, где car_id =: id» ДЛЯ КАЖДОГО автомобиля.

Это делает первый выбор и более 1 выбор для каждой N машин, поэтому это называется проблемой n + 1.

Чтобы избежать этого, заставьте ассоциацию извлекаться как активную, чтобы hibernate загружал данные с объединением.

Но обратите внимание, если много раз вы не обращаетесь к связанным колесам, лучше оставить их LAZY или изменить тип выборки с помощью Criteria.

    
1
2013-07-12 17: 58: 36Z
  1. Опять же, объединения не являются хорошим решением, особенно когда может быть загружено более 2 уровней иерархии. Вместо этого отметьте «subselect» или «batch-size»; последний будет загружать дочерние элементы по родительским идентификаторам в предложении «in», например «select ... from wheel, где car_id in (1,3,4,6,7,8,11,13)».
    2013-10-03 20: 29: 31Z
источник размещен Вот