10

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

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 соответственно.)

Edit: В 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 

Edit: Благодаря Алексу и ГБН указал мне в правильном направлении. Я также нашел this question.

Вот новый запрос:

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

ответ

6

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

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 
+0

Это зависит от структуры таблиц. Вы верны для случая T1-T2, T2-T3. В моей ситуации это T1-T2, T1-T3. – geofftnz

+0

@geofftnz: см. Мой ответ. Вы не * T1-T2, T1-T3 – gbn

2

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

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

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

+0

Спасибо за вход Эрик. Я собираюсь сделать еще несколько повторных встреч, чтобы увидеть, могу ли я снизить стоимость. На всех столбцах, используемых для объединения, есть индексы.К сожалению, характер данных означает, что я должен использовать внешние соединения для этого. – geofftnz

+0

«Очевидно, оптимизатор SQL Server 2005 намного лучше, чем SQL Server 2000». Это преуменьшение. Мало того, что оптимизатор запросов SQL Server 2000 слабый (наряду с остальной версией 2000 года, на мой взгляд), он часто кажется чрезмерно оптимизированным, делая ложные предположения, которые приводят к смехотворно неверным данным. Объединение представлений или представление, содержащее объединение, или представление, содержащее представление (!), Могут инициировать этот идиотизм после пересечения определенного порога сложности. Я никогда не мог точно определить характер проблемы, но меня часто укусили. – WCWedin

1

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

2

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

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

1

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

2

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

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

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.

+0

Какие ситуации могут привести к тому, что эти два запроса возвращают разные результаты? – geofftnz

+0

Точно так же, как показал Алекс ... – gbn

+0

Но в ситуации T1-T2 T2-T3, если между T2 и T3 была связь внешнего ключа, так что строка в T3 не может существовать без ссылки на строку в T2, все еще имеет значение ? – geofftnz

3

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

<joined_table>

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

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

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

 Смежные вопросы

  • Нет связанных вопросов^_^