Какой самый быстрый способ избежать проблем с n + 1 и почему? - PullRequest
5 голосов
/ 23 сентября 2011

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

Общая схема такова:

select a.* /* over 10 columns */
from [table-A] a
where /* something */

Извлечено в коллекцию ClassA экземпляров записей

Затем подэкземпляры извлекаются лениво:

select b.* /* over 10 columns */
from [sub-table-B] b
where b.ParentId = @ClassA_ID

Это приводит к проблеме выбора n + 1.В большинстве случаев это не является серьезной проблемой, поскольку на нечасто посещаемых страницах извлекается только пара ClassA экземпляров, но во все большем числе мест эта проблема n + 1 приводит к тому, что страницы становятся слишком медленными из-за масштабирования приложения,

Я собираюсь заменить часть существующего кода доступа к данным этого приложения, чтобы экземпляры ClassA и ClassB извлекались вместе.

Я думаю, что есть 3как это можно сделать:

1) Получить экземпляры ClassA, как мы делаем сейчас, затем получить экземпляры ClassB в одном агрегированном вызове:

select b.*
from [sub-table-B] b
where b.ParentId in ( /* list of parent IDs */ )

Это два отдельныхВызовы БД, и план запроса динамического SQL не будет кэшироваться (из-за списка идентификаторов).

2) Получить экземпляры ClassB с подзапросом:

select b.*
from [sub-table-B] b
    inner join [table-A] a
        on b.ParentId = a.[ID]
where /* something */

Это также два вызова БД, и запрос к [table-A] должен оцениваться дважды.

3) Собрать все вместе и исключить дублирование ClassA экземпляров:

select a.*, b.*
from [table-A] a
    left outer join [sub-table-B] b 
        on a.[ID] = b.ParentId
where /* something */

Это всего лишь один вызов БД, но теперь мы получаем повторное содержимое [table-A] - набор результатов будет больше, а время отправки данных из БД клиенту будет больше.

Так что на самом делеэто 3 возможных компромисса:

  1. 2 вызова БД, без кэширования запросов
  2. 2 вызова БД, сложный запрос оценивается дважды
  3. 1 вызов БД, значительно больший набор результатов

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

Что используют существующие механизмы, такие как Linq, EF и NHibernate?

Существует ли 4-й способ, который лучше всех 3?

Ответы [ 3 ]

1 голос
/ 23 сентября 2011

Я думаю, что EF и L2S используют ваш третий подход - это определенно только один вызов дБ.

Обычно большее количество циклов дб занимает больше времени, чем меньшее количество циклов дб с большими наборами результатов.

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

Но это в основном вопрос задержки и пропускной способности между сервером БД и клиентом.

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

0 голосов
/ 23 сентября 2011

Большинство современных баз данных (Oracle наверняка, если вы используете параметризованные запросы) будут кэшировать оценку запроса, и вы столкнетесь с очень небольшим ударом по ним.

Некоторые ORM, такие как Django , позволятсоздать собственный запрос и вернуть только частичные результаты, которые вам нужны для отображения страницы.Это хороший подход - если вы видите, что точка доступа к БД оптимизирует ее, но в противном случае оставьте ORM, чтобы делать ставки.

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

0 голосов
/ 23 сентября 2011

По моему мнению, "какой самый быстрый путь" зависит от задержки и пропускной способности вашего сервера базы данных, а также от того, насколько велики ваши наборы результатов.

В случае, когда задержка является узким местом (сеть ADSL?), И если ваши наборы результатов невелики, вам лучше отправить один единственный запрос на ваш сервер. Используемая пропускная способность будет больше из-за того, что [table-A] запись отправляется несколько раз, но в целом это может быть самый быстрый способ передать ваши данные клиенту.

...