INNER JOIN vs LEFT JOIN в SQL Server

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

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

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

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

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

Ответ 1

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

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

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


Edit:

Размышляя об этом, я мог бы подумать об одном обстоятельстве, при котором < <20 > может быть быстрее, чем 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) для хэш-таблицы.

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

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

Ответ 2

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

При использовании внешнего соединения оптимизатор всегда может отбрасывать внешнюю объединенную таблицу из плана выполнения, если столбцы соединения являются PK внешней таблицы, и ни один из столбцов не выбран из внешней таблицы. Например, SELECT A.* FROM A LEFT OUTER JOIN B ON A.KEY=B.KEY и B.KEY - это PK для 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.

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

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

Очень важно убедиться, что представление с использованием внешних соединений дает правильные результаты. Как сказал Ааронок, вы не можете вслепую замещать ВНЕШНЮЮ ПРИСОЕДИНЯЮТСЯ ДЛЯ ВНУТРЕННЕГО ПРИСОЕДИНЕНИЯ и ожидать тех же результатов. Но есть моменты, когда это может быть полезно по соображениям производительности при использовании представлений.

Одна последняя заметка - я не тестировал влияние на производительность в свете вышеизложенного, но теоретически кажется, что вы должны быть в состоянии безопасно заменить INNER JOIN с помощью OUTER JOIN, если вы также добавите условие < FOREIGN_KEY > НЕТ NULL для предложения where.

Ответ 3

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

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

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

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

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

НО, оптимизатор также может оптимизировать левое соединение как оптимальное как левое полусоединение. Чтобы выбрать тот, который вы хотите, вы можете использовать подсказку указания порядка.

Ответ 4

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

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

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

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

Ответ 5

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

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

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

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

Ответ 6

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

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

Ответ 7

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

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

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

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

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

Ответ 8

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

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

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