Почему порядок предложений соединения влияет на план запроса в SQL Server?

Я создаю представление в SQL Server 2000 (и 2005), и я заметил, что порядок операторов соединения сильно влияет на план выполнения и скорость запроса.

select      sr.WTSASessionRangeID,
            -- bunch of other columns
from        WTSAVW_UserSessionRange us
inner join  WTSA_SessionRange sr on sr.WTSASessionRangeID = us.WTSASessionRangeID
left outer join WTSA_SessionRangeTutor srt on srt.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join WTSA_SessionRangeClass src on src.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join WTSA_SessionRangeStream srs on srs.WTSASessionRangeID = sr.WTSASessionRangeID
--left outer join MO_Stream ms on ms.MOStreamID = srs.MOStreamID
left outer join WTSA_SessionRangeEnrolmentPeriod srep on srep.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join WTSA_SessionRangeStudent stsd on stsd.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join WTSA_SessionSubrange ssr on ssr.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join WTSA_SessionSubrangeRoom ssrr on ssrr.WTSASessionSubrangeID = ssr.WTSASessionSubrangeID
left outer join MO_Stream ms on ms.MOStreamID = srs.MOStreamID

В SQL Server 2000 запрос выше последовательно генерирует план стоимости 946. Если я раскомментирую соединение MO_Stream в середине запроса и закомментирую его внизу, стоимость снижается до 263. Скорость выполнения падает соответственно. Я всегда думал, что оптимизатор запросов будет интерпретировать запрос соответствующим образом, не учитывая порядок объединения, но кажется, что порядок имеет значение.

Итак, поскольку порядок действительно имеет значение, существует ли стратегия объединения, которую я должен выполнять для написания более быстрых запросов?

(Кстати, на SQL Server 2005 с почти идентичными данными стоимость плана запроса составляла 0,675 и 0,631 соответственно.)

Изменить: В SQL Server 2000, вот профилированная статистика:

  • 946-cost query: 9094ms CPU, 5121 reads, 0 writes, 10123ms duration
  • 263-cost query: 172ms CPU, 7477 reads, 0 writes, 170ms duration

Изменить: Вот логическая структура таблиц.

SessionRange ---+--- SessionRangeTutor
                |--- SessionRangeClass
                |--- SessionRangeStream --- MO_Stream
                |--- SessionRangeEnrolmentPeriod
                |--- SessionRangeStudent
                +----SessionSubrange --- SessionSubrangeRoom

Изменить: Спасибо Alex и gbn за то, что указали мне в правильном направлении. Я также нашел этот вопрос.

Здесь новый запрос:

select sr.WTSASessionRangeID    // + lots of columns

from WTSAVW_UserSessionRange us
inner join WTSA_SessionRange sr on sr.WTSASessionRangeID = us.WTSASessionRangeID
left outer join WTSA_SessionRangeTutor srt on srt.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join WTSA_SessionRangeClass src on src.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join WTSA_SessionRangeEnrolmentPeriod srep on srep.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join WTSA_SessionRangeStudent stsd on stsd.WTSASessionRangeID = sr.WTSASessionRangeID

// SessionRangeStream is a many-to-many mapping table between SessionRange and MO_Stream
left outer join (
    WTSA_SessionRangeStream srs
    inner join MO_Stream ms on ms.MOStreamID = srs.MOStreamID
) on srs.WTSASessionRangeID = sr.WTSASessionRangeID

// SessionRanges MAY have Subranges and Subranges MAY have Rooms
left outer join (
    WTSA_SessionSubrange ssr    
    left outer join WTSA_SessionSubrangeRoom ssrr on ssrr.WTSASessionSubrangeID = ssr.WTSASessionSubrangeID
) on ssr.WTSASessionRangeID = sr.WTSASessionRangeID

Стоимость SQLServer2000: 24,9

Ответ 1

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

SELECT 1 AS a INTO #t1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4;

SELECT 1 AS b INTO #t2
UNION ALL SELECT 2;

SELECT 1 AS c INTO #t3
UNION ALL SELECT 3;

SELECT a, b, c 
FROM #t1 LEFT JOIN #t2 ON #t1.a=#t2.b
  LEFT JOIN #t3 ON #t2.b=#t3.c
ORDER BY a;

SELECT a, b, c 
FROM #t1 LEFT JOIN #t3 ON #t1.a=#t3.c
  LEFT JOIN #t2 ON #t3.c=#t2.b
ORDER BY a;

a           b           c
----------- ----------- -----------
1           1           1
2           2           NULL
3           NULL        NULL
4           NULL        NULL

(4 row(s) affected)

a           b           c
----------- ----------- -----------
1           1           1
2           NULL        NULL
3           NULL        3
4           NULL        NULL

Ответ 2

Порядок соединения действительно влияет на результирующий запрос. Это описано в документе BOL в документах для FROM:

<joined_table >

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

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

Это называется хиативным поведением. Вы также можете использовать подсказку запроса OPTION (FORCE ORDER), чтобы принудительно выполнить определенный порядок соединения, но это может привести к так называемым "кустистым планам", которые могут быть не самыми оптимальными для выполняемого запроса.

Ответ 3

Очевидно, оптимизатор SQL Server 2005 намного лучше, чем SQL Server 2000.

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

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

Ответ 4

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

Наряду с вышеуказанной оптимизацией убедитесь, что все поля, используемые в JOINs, индексируются

Ответ 5

В любом случае, вы, вероятно, ошибаетесь. Алекс прав. Эрик тоже может быть прав, но запрос неверен.

Позволяет взять это подмножество:

WTSA_SessionRange sr
left outer join
WTSA_SessionSubrange ssr on ssr.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join
WTSA_SessionSubrangeRoom ssrr on ssrr.WTSASessionSubrangeID = ssr.WTSASessionSubrangeID

Вы присоединяетесь к WTSA_SessionSubrangeRoom на WTSA_SessionSubrange. У вас может не быть строк из WTSA_SessionSubrange.

Соединение должно быть следующим:

WTSA_SessionRange sr
left outer join
(SELECT WTSASessionRangeID, columns I need
FROM
    WTSA_SessionSubrange ssr
    left outer join
    WTSA_SessionSubrangeRoom ssrr on ssrr.WTSASessionSubrangeID = ssr.WTSASessionSubrangeID
) foo on foo.WTSASessionRangeID = sr.WTSASessionRangeID

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

Вам также потребуется изменить соединения MO_Stream и WTSA_SessionRangeStream.

Ответ 6

это зависит от того, какое из полей объединения индексируется - если оно должно сканировать первое поле, но использовать индекс на втором, оно медленное. Если ваше первое поле объединения является индексом, это будет быстрее. Я предполагаю, что 2005 оптимизирует его лучше, определяя индексированные поля и выполняя первые

Ответ 7

В DevConnections несколько лет назад сессия по производительности SQL Server заявила, что (a) порядок внешних объединений имеет значение, и (б) когда запрос имеет много соединений, он не будет смотреть на всех из них, прежде чем определение плана. Если вы знаете, что у вас есть подключения, которые помогут ускорить запрос, они должны быть ранними в списке FROM (если возможно).