ВНУТРЕННЕЕ СОЕДИНЕНИЕ И ПРОИЗВОДИТЕЛЬНОСТЬ ЛЕВОГО СОЕДИНЕНИЯ в SQL Server

259

Я создал команду 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
Вы проецируете какой-либо атрибут coUOM? Если нет, вы можете использовать полу соединение. Если да, вы сможете использовать UNIONв качестве альтернативы. Публикация только вашего FROMпредложения является неадекватной информацией здесь.
onedaywhen
1
Я так часто удивляюсь (потому что вижу все время).
Пол Дрейпер
1
Вы пропустили Order By в своей краткой схеме? Я только недавно столкнулся с проблемой, когда изменение INNER JOIN на LEFT OUTER JOIN ускоряет запрос с 3 минут до 10 секунд. Если в вашем запросе действительно есть Order By, я объясню далее в качестве ответа. Похоже, что все ответы на самом деле не объясняют случай, с которым я столкнулся.
Фуах Йи Кит

Ответы:

403

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

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

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


Редактировать:

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

Aaronaught
источник
4
Существует еще один сценарий, который может привести к тому, что OUTER JOIN будет работать лучше, чем INNER JOIN. Смотрите мой ответ ниже.
ДБЕНХАМ
12
Я хочу отметить, что в основном нет документации по базе данных, которая бы поддерживала идею о том, что производительность внутренних и внешних объединений различна. Внешние объединения немного дороже, чем внутренние, из-за объема данных и размера результирующего набора. Однако базовые алгоритмы ( msdn.microsoft.com/en-us/library/ms191426(v=sql.105).aspx ) одинаковы для обоих типов объединений. Производительность должна быть одинаковой, когда они возвращают одинаковые объемы данных.
Гордон Линофф
3
@Aaronaught. , , На этот ответ была дана ссылка в комментарии, в котором говорилось о том, что «внешние соединения работают значительно хуже внутренних соединений». Я прокомментировал просто, чтобы быть уверенным, что это неправильное толкование не распространяется.
Гордон Линофф
16
Я думаю, что этот ответ вводит в заблуждение в одном важном аспекте: поскольку в нем говорится, что «левое соединение абсолютно не быстрее, чем внутреннее соединение». Эта строка не верна. Это теоретически не быстрее, чем внутреннее соединение. Это НЕ «абсолютно не быстрее». Вопрос, в частности, вопрос производительности. На практике я уже видел несколько систем (очень крупных компаний!), В которых INNER JOIN был смехотворно медленным по сравнению с OUTER JOIN. Теория и практика очень разные вещи.
Дэвид Френкель
5
@DavidFrenkel: это очень маловероятно. Я бы попросил увидеть сравнение А / Б с планами выполнения, если вы считаете, что такое расхождение возможно. Возможно, это связано с кэшированными планами запросов / выполнения или плохой статистикой.
Aaronaught
127

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

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

dbenham
источник
5
Я действительно столкнулся с этой проблемой при создании чрезвычайно динамических запросов. Я оставил ВНУТРЕННЕЕ СОЕДИНЕНИЕ, которое использовал, а не извлекал данные, и когда я переключил его на ЛЕВОЕ СОЕДИНЕНИЕ (из любопытства сдвига), запрос фактически выполнялся быстрее.
Эрик Филипс
1
РЕДАКТИРОВАТЬ - Уточнил условия, которые должны существовать, чтобы оптимизатор удалил внешнюю объединенную таблицу из плана выполнения.
ДБЕНХАМ
2
Одно небольшое пояснение к вашему ответу: когда столбец внешнего ключа не имеет значения NULL, INNER JOIN и LEFT JOIN становятся семантически эквивалентными (т. Е. Предложенное предложение WHERE является избыточным); единственная разница будет в плане выполнения.
Дуглас
2
Хотя это показывает, казалось бы, тривиальный пример, это чрезвычайно проницательный ответ!
Пбалага,
6
+1: я, кажется, столкнулся с этим в нескольких запросах, где я использовал внутренние объединения с некоторыми очень большими таблицами. Внутреннее соединение вызывало утечку в базу данных tempdb в плане запросов (я полагаю, по причине, указанной выше - и на моем сервере не хватало оперативной памяти для хранения всего в памяти). Переключение на левые соединения устранило утечку в базу данных tempdb, в результате чего некоторые из моих 20-30-секундных запросов теперь выполняются за доли секунды. Это очень важный момент, так как большинство людей, кажется, делают общее предположение, что внутренние соединения происходят быстрее.
Фосфлайт
23

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

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

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

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

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

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

Kvasi
источник
18

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

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

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

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

Франсиско Пирес
источник
9

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

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

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

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

ДЖО
источник
8

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

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

eddiegroves
источник
7

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

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

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

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

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

MarredCheese
источник
1
Ваше утверждение о "внешнем соединении" вводит в заблуждение и потенциально неверно. Внешний означает, что данные на другой стороне не должны существовать - и если они не заменяют NULL. При определенных обстоятельствах СУБД может «пропустить» их (см. Ответ от dbenham выше). ОДНАКО - внешний и внутренний может привести к тому, что ваш запрос выдаст совершенно разные результаты. ВНУТРЕННИЙ означает - дать результаты, для которых элемент находится в ОБА A и B. LEFT OUTER означает все A, и, необязательно, B, если он существует. В первом случае вы получаете несколько строк, во втором вы получаете ВСЕ строки.
ripvlan
1
@ripvlan Конечно, внешние и внутренние соединения не всегда взаимозаменяемы. Первоначальный вопрос был о производительности, что подразумевает, что мы говорим о случаях, когда любое объединение вернет один и тот же набор результатов.
MarredCheese
1
Да и - OUTER может вызвать проблемы с производительностью, потому что это приведет к возвращению всех строк (больше данных). Ваше предположение о том, что запросы приводят к одному и тому же выводу, справедливо - однако это не так в общем случае и не относится к каждому дизайну БД. И для тех, кто не на 100% знаком с реляционной алгеброй, это может вызвать у них горе. Моя цель состоит только в том, чтобы предложить больше понимания людям, читающим это в поисках совета, и что ВЛЕВО / ВПРАВО волшебным образом не решит проблему и может вызвать больше проблем. Это сила, оставленная для уровня 300 :-)
ripvlan
2

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

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

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

Buzzzzzzz
источник
0

Из моих сравнений я обнаружил, что у них точно такой же план выполнения. Есть три сценария:

  1. Если и когда они возвращают одинаковые результаты, они имеют одинаковую скорость. Однако мы должны помнить, что это не одинаковые запросы, и что LEFT JOIN, возможно, вернет больше результатов (если не выполнены некоторые условия ON), поэтому обычно он медленнее.

  2. Когда основная таблица (первая неконстантная в плане выполнения) имеет ограничительное условие (WHERE id =?) И соответствующее условие ON имеет значение NULL, «правая» таблица не объединяется - это когда LEFT JOIN быстрее.

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

Оба используют (одинаковые) индексы.

Жюлин Тен
источник