Внутреннее соединение vs левая производительность соединения в SQL Server

Я создал команду SQL, которая использует внутреннее соединение для 9 таблиц, в любом случае эта команда занимает очень много времени (более пяти минут). Поэтому мой народ предлагает мне изменить внутреннее соединение на левое, потому что исполнение левого соединения лучше, на первый раз, несмотря на то, что я знаю. После того, как я изменился, скорость запроса значительно улучшилась.

Я хотел бы знать, почему LEFT JOIN быстрее, чем INNER JOIN?

моя команда SQL выглядит следующим образом: SELECT * FROM A INNER JOIN B ON ... INNER JOIN C ON ... INNER JOIN D и так on

обновление: Это краткое изложение моей схемы.

FROM sidisaleshdrmly a -- NOT HAVE PK AND FK
    INNER JOIN sidisalesdetmly b -- THIS TABLE ALSO HAVE NO PK AND FK
        ON a.CompanyCd = b.CompanyCd 
           AND a.SPRNo = b.SPRNo 
           AND a.SuffixNo = b.SuffixNo 
           AND a.dnno = b.dnno
    INNER JOIN exFSlipDet h -- PK = CompanyCd, FSlipNo, FSlipSuffix, FSlipLine
        ON a.CompanyCd = h.CompanyCd
           AND a.sprno = h.AcctSPRNo
    INNER JOIN exFSlipHdr c -- PK = CompanyCd, FSlipNo, FSlipSuffix
        ON c.CompanyCd = h.CompanyCd
           AND c.FSlipNo = h.FSlipNo 
           AND c.FSlipSuffix = h.FSlipSuffix 
    INNER JOIN coMappingExpParty d -- NO PK AND FK
        ON c.CompanyCd = d.CompanyCd
           AND c.CountryCd = d.CountryCd 
    INNER JOIN coProduct e -- PK = CompanyCd, ProductSalesCd
        ON b.CompanyCd = e.CompanyCd
           AND b.ProductSalesCd = e.ProductSalesCd 
    LEFT JOIN coUOM i -- PK = UOMId
        ON h.UOMId = i.UOMId 
    INNER JOIN coProductOldInformation j -- PK = CompanyCd, BFStatus, SpecCd
        ON a.CompanyCd = j.CompanyCd
            AND b.BFStatus = j.BFStatus
            AND b.ProductSalesCd = j.ProductSalesCd
    INNER JOIN coProductGroup1 g1 -- PK = CompanyCd, ProductCategoryCd, UsedDepartment, ProductGroup1Cd
        ON e.ProductGroup1Cd  = g1.ProductGroup1Cd
    INNER JOIN coProductGroup2 g2 -- PK = CompanyCd, ProductCategoryCd, UsedDepartment, ProductGroup2Cd
        ON e.ProductGroup1Cd  = g2.ProductGroup1Cd

8 ответов


A LEFT JOIN абсолютно не быстрее, чем INNER JOIN. На самом деле, это медленнее; по определению, внешнее соединение (LEFT JOIN или RIGHT JOIN) должен делать ВСЮ работу INNER JOIN плюс дополнительная работа нулевого расширения результатов. Ожидается также, что будет возвращено больше строк, что еще больше увеличит общее время выполнения просто из-за большего размера результирующего набора.

(и даже если LEFT JOIN были быстрее конкретные ситуаций из-за некоторых трудно себе представить слияние факторов, оно функционально не эквивалентно INNER JOIN, поэтому вы не можете просто заменить все экземпляры одного на другой!)

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


Edit:

размышляя об этом, я мог бы подумать об одном обстоятельстве, при котором LEFT JOIN может быть быстрее, чем INNER JOIN и:

  • некоторые из таблиц очень маленький (скажем, под 10 рядами);
  • таблицы не имеют достаточных индексов для покрытия запроса.

Рассмотрим пример:

CREATE TABLE #Test1
(
    ID int NOT NULL PRIMARY KEY,
    Name varchar(50) NOT NULL
)
INSERT #Test1 (ID, Name) VALUES (1, 'One')
INSERT #Test1 (ID, Name) VALUES (2, 'Two')
INSERT #Test1 (ID, Name) VALUES (3, 'Three')
INSERT #Test1 (ID, Name) VALUES (4, 'Four')
INSERT #Test1 (ID, Name) VALUES (5, 'Five')

CREATE TABLE #Test2
(
    ID int NOT NULL PRIMARY KEY,
    Name varchar(50) NOT NULL
)
INSERT #Test2 (ID, Name) VALUES (1, 'One')
INSERT #Test2 (ID, Name) VALUES (2, 'Two')
INSERT #Test2 (ID, Name) VALUES (3, 'Three')
INSERT #Test2 (ID, Name) VALUES (4, 'Four')
INSERT #Test2 (ID, Name) VALUES (5, 'Five')

SELECT *
FROM #Test1 t1
INNER JOIN #Test2 t2
ON t2.Name = t1.Name

SELECT *
FROM #Test1 t1
LEFT JOIN #Test2 t2
ON t2.Name = t1.Name

DROP TABLE #Test1
DROP TABLE #Test2

если вы запустите этот и посмотреть план выполнения, вы увидите, что INNER JOIN запрос действительно стоит больше, чем LEFT JOIN, потому что он удовлетворяет двум вышеуказанным критериям. Это потому, что SQL Server хочет сделать хэш-матч для INNER JOIN, но не вложенные циклы для LEFT JOIN; бывший обычно гораздо быстрее, но так как количество строк так мало и нет индекса для использования, операция хэширования оказывается самой дорогой частью запроса.

вы можно увидеть тот же эффект, написав программу на вашем любимом языке программирования, чтобы выполнить большое количество поисков в списке с 5 элементами против хэш-таблицы с 5 элементами. Из-за размера версия хэш-таблицы на самом деле медленнее. Но увеличьте его до 50 элементов или 5000 элементов, и версия списка замедлится до обхода, потому что это O(N) против O(1) для хэш-таблицы.

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

таким образом, вывод более или менее то, что я упомянул несколько абзацев выше; это почти наверняка проблема индексации или индексного покрытия, возможно, в сочетании с одной или несколькими очень маленькими таблицами. Это единственные обстоятельства, при которых SQL Server может иногда выбирайте худший план выполнения для INNER JOIN чем LEFT JOIN.


существует один важный сценарий, который может привести к внешнему соединению быстрее, чем внутреннее соединение, которое еще не обсуждалось.

при использовании внешнего соединения оптимизатор всегда может удалить внешнюю объединенную таблицу из плана выполнения, если столбцы соединения являются PK внешней таблицы, и ни один из столбцов не выбран из внешней таблицы. Например SELECT A.* FROM A LEFT OUTER JOIN B ON A.KEY=B.KEY и B. ключ-это ПК для B. Как Oracle (я считаю, что использовал выпуск 10), так и Sql Server (я использовал 2008 R2) вырежьте таблицу B из плана выполнения.

то же самое не обязательно верно для внутреннего соединения:SELECT A.* FROM A INNER JOIN B ON A.KEY=B.KEY может или не может требовать B в плане выполнения в зависимости от того, какие ограничения существуют.

Если A. KEY является нулевым внешним ключом, ссылающимся на B. KEY, оптимизатор не может удалить B из плана, потому что он должен подтвердить, что строка B существует для каждой строки A.

Если A. KEY является обязательным внешним ключом, ссылающимся на B. KEY, то оптимизатор может свободно удалить B из план, потому что ограничения гарантируют существование строки. Но только потому, что оптимизатор может удалить таблицу из плана, не означает, что это будет. SQL Server 2008 R2 не удаляет B из плана. Oracle 10 действительно удаляет B из плана. В этом случае легко увидеть, как внешнее соединение будет выполнять внутреннее соединение на SQL Server.

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

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

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

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


Если все работает так, как должно, это не должно, но мы все знаем, что все не работает так, как должно, особенно когда дело доходит до оптимизатора запросов, кэширования плана запросов и статистики.

сначала я бы предложил перестроить индекс и статистику, а затем очистить кэш плана запроса, чтобы убедиться, что это не испортит ситуацию. Однако я испытал проблемы, даже когда это сделано.

Я испытывал некоторые случаи, когда левое соединение было быстрее, чем внутреннее соединение.

основная причина заключается в следующем: Если у вас есть две таблицы, и вы вступите в столбце с индексом (в обеих таблицах). Внутреннее соединение будет производить тот же результат независимо от того, если вы цикл по записям в индекс на таблице один и матч с индексом на таблице два, как если бы вы делали наоборот: цикл по записям в индексе на стол и два матча с индексом в таблице один. Проблема в том, что при наличии вводящей в заблуждение статистики оптимизатор запросов будет использовать статистику индекс, чтобы найти таблицу с наименьшими совпадающими записями (на основе других критериев). Если у вас есть две таблицы с 1 миллионом в каждой, в таблице один у вас есть 10 совпадающих строк и в таблице два у вас есть 100000 совпадающих строк. Лучшим способом было бы выполнить сканирование индекса по таблице один и сопоставить 10 раз в таблице два. Обратным было бы сканирование индекса, которое петляет по 100000 строкам и пытается соответствовать 100000 раз и только 10 успешно. Поэтому, если статистика неверна, оптимизатор может выбрать неправильный таблица и индекс для цикла.

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

но оптимизатор также может оптимизировать левое соединение суб-оптимально как левое полусоединение. Чтобы сделать его выбрать тот, который вы хотите, вы можете использовать подсказку force order.


попробуйте оба запроса (один с внутренним и левым соединением) с OPTION (FORCE ORDER) в конце и опубликовать результаты. OPTION (FORCE ORDER) - подсказка запроса, которая заставляет оптимизатор строить план выполнения с порядком соединения, указанным в запросе.

если INNER JOIN начинает выполнять так же быстро, как LEFT JOIN, это потому, что:

  • в запросе, составленном полностью INNER JOINs, порядок соединения не имеет значения. Это дает оптимизатору запросов свободу упорядочивать соединения по своему усмотрению, таким образом, проблема может зависеть от оптимизатора.
  • С LEFT JOIN, это не так, потому что изменение порядка соединения изменит результаты запроса. Это означает, что движок должен следовать порядку соединения, указанному в запросе, который может быть лучше оптимизированного.

Не знаю, отвечает ли это на ваш вопрос, но я однажды был в проекте, который отличался очень сложными запросами, производящими вычисления, которые полностью испортили оптимизатор. У нас были дела где а FORCE ORDER сократит время выполнения запроса с 5 минут до 10 секунд.


сделали ряд сравнений между левыми внешними и внутренними соединениями и не смогли найти разницу в консистенции. Есть много переменных. Я работаю над базой данных отчетов с тысячами таблиц многие с большим количеством полей, много изменений с течением времени (версии поставщиков и локальный рабочий процесс) . Невозможно создать все комбинации охватывающих индексов для удовлетворения потребностей такого широкого круга запросов и обработки исторических данных. Видели внутренние запросы убейте производительность сервера, потому что две большие (от миллионов до десятков миллионов строк) таблицы являются внутренними Соединенными и тянут большое количество полей, и нет индекса покрытия.

самая большая проблема, кажется, не appeaer в вышеупомянутых дискуссиях. Возможно, ваша база данных хорошо разработана с триггерами и хорошо продуманной обработкой транзакций для обеспечения хороших данных. Мой часто имеет значения NULL, где они не ожидаются. Да, определения таблиц могут применять no-Nulls, но это не вариант в моем окружении.

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

вы можете быть очень быстро, как получить 90% необходимых данных и не обнаружить внутренние соединения молча удалили информацию. Иногда внутренние соединения могут быть быстрее, но я не верю, что кто-то делает это предположение, если они не пересмотрели план выполнения. Скорость важна, но точность важнее.


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

в худшем случае вы можете легко сделать 9 всего сканирования таблицы для каждого соединения.


внешние соединения могут обеспечить превосходную производительность при использовании в представлениях.

скажем, у вас есть запрос, который включает представление, и это представление состоит из 10 таблиц, соединенных вместе. Скажем, ваш запрос использует только столбцы из 3 из этих 10 таблиц.

Если бы эти 10 таблиц были внутренний-присоединился вместе, то оптимизатор запросов должен был бы присоединиться к ним всем, даже если ваш запрос сам по себе не нуждается в 7 из 10 таблиц. Это потому что внутреннее сами соединения могут фильтровать данные, что делает их необходимыми для вычисления.

Если бы эти 10 таблиц были внешняя-присоединился вместо этого оптимизатор запросов будет фактически присоединяться только к тем, которые необходимы: 3 из 10 из них в этом случае. Это связано с тем, что сами соединения больше не фильтруют данные, и поэтому неиспользуемые соединения могут быть пропущенный.

источник: http://www.sqlservercentral.com/blogs/sql_coach/2010/07/29/poor-little-misunderstood-views/


Я нашел что-то интересное в SQL server при проверке, если внутренние соединения быстрее, чем левые соединения.

Если вы не включаете элементы левой присоединенной таблицы, в инструкции select левое соединение будет быстрее, чем тот же запрос с внутренним соединением.

Если вы включаете таблицу с левым соединением в инструкцию select, внутреннее соединение с тем же запросом было равно или быстрее, чем левое соединение.