Что такое «проблема выбора N + 1» в ORM (объектно-реляционное отображение)? - PullRequest
1445 голосов
/ 19 сентября 2008

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

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

Ответы [ 16 ]

10 голосов
/ 20 февраля 2009

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

http://pramatr.com/2009/02/05/sql-n-1-selects-explained/

9 голосов
/ 07 ноября 2014

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

9 голосов
/ 29 марта 2013

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

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

Расходы:

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

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

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

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

Резюме

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

8 голосов
/ 15 апреля 2015

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

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

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

@Expectation(atMost = 3)
@Test
public void testInvokingDatabase() {
    // your JDBC or JPA code
}
5 голосов
/ 17 октября 2012

Проблема, как другие более элегантно заявили, заключается в том, что у вас либо декартово произведение столбцов 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 различных элементов для какой-либо массовой обработки. Поместите 100 различных идентификаторов во временную таблицу.

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

1 голос
/ 12 июля 2013

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

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

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

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

...