IN vs. JOIN с большими наборами строк - PullRequest
28 голосов
/ 16 июня 2009

Я хочу выбрать строки в таблице, где первичный ключ находится в другой таблице. Я не уверен, должен ли я использовать оператор JOIN или IN в SQL Server 2005. Есть ли существенная разница в производительности между этими двумя запросами SQL с большим набором данных (т.е. миллионами строк)?

SELECT *
FROM a
WHERE a.c IN (SELECT d FROM b)

SELECT a.*
FROM a JOIN b ON a.c = b.d

Ответы [ 12 ]

29 голосов
/ 16 июня 2009

Обновление:

Эта статья в моем блоге суммирует мой ответ и мои комментарии к другим ответам, а также фактические планы выполнения:


SELECT  *
FROM    a
WHERE   a.c IN (SELECT d FROM b)

SELECT  a.*
FROM    a
JOIN    b
ON      a.c = b.d

Эти запросы не эквивалентны. Они могут давать разные результаты, если ваша таблица b не сохранена на ключе (т.е. значения b.d не являются уникальными).

Эквивалентом первого запроса является следующее:

SELECT  a.*
FROM    a
JOIN    (
        SELECT  DISTINCT d
        FROM    b
        ) bo
ON      a.c = bo.d

Если b.d равен UNIQUE и помечен как таковой (UNIQUE INDEX или UNIQUE CONSTRAINT), то эти запросы идентичны и, скорее всего, будут использовать идентичные планы, поскольку SQL Server достаточно умен, чтобы принять это в счет.

SQL Server может использовать один из следующих методов для выполнения этого запроса:

  • Если для a.c есть индекс, d равен UNIQUE и b относительно мал по сравнению с a, то условие распространяется в подзапрос и на простую INNER JOIN используется (с b начальным)

  • Если для b.d есть индекс, а d не равен UNIQUE, то условие также распространяется и используется LEFT SEMI JOIN. Может также использоваться для вышеуказанного условия.

  • Если для b.d и a.c есть индексы и они большие, то используется MERGE SEMI JOIN

  • Если в какой-либо таблице нет индекса, то хеш-таблица строится на b и используется HASH SEMI JOIN.

Ни один из этих методов не переоценивает весь подзапрос каждый раз.

Смотрите эту запись в моем блоге для более подробной информации о том, как это работает:

Есть ссылки для всех RDBMS из большой четверки.

5 голосов
/ 16 июня 2009

Ни. Используйте ANSI-92 JOIN:

SELECT a.*
FROM a JOIN b a.c = b.d

Впрочем, лучше всего СУЩЕСТВУЕТ

SELECT a.*
FROM a
WHERE EXISTS (SELECT * FROM b WHERE a.c = b.d)

Это удаляет дубликаты, которые могут быть сгенерированы JOIN, но работает так же быстро, если не быстрее

4 голосов
/ 18 июня 2009

Исходя из опыта работы с таблицей из 49 000 000 строк, я бы порекомендовал ЛЕВОЕ ВНЕШНЕЕ СОЕДИНЕНИЕ. Использование IN или EXISTS Потребовалось 5 минут, чтобы завершить, когда LEFT OUTER JOIN заканчивается через 1 секунду.

SELECT a.*
FROM a LEFT OUTER JOIN b ON a.c = b.d
WHERE b.d is not null -- Given b.d is a primary Key with index

На самом деле в моем запросе я делаю это для 9 таблиц.

4 голосов
/ 16 июня 2009

IN оценивается (и повторно выбирается из b) для каждой строки в a, тогда как JOIN оптимизирован для использования индексов и других хитрых трюков подкачки ...

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

Редактировать: Пожалуйста, прочитайте комментарии ниже для дальнейшего ... обсуждение действительности этогоответ и фактический ответ на вопрос ОП.=)

2 голосов
/ 16 июня 2009

С Документация MSDN по основам подзапроса:

Многие операторы Transact-SQL, которые включить подзапросы можно альтернативно сформулированы как соединения. Другие вопросы могут быть заданы только с подзапросы. В Transact-SQL есть обычно нет разницы в производительности между утверждением, которое включает в себя подзапрос и семантически эквивалентный Версия, которой нет. Однако в в некоторых случаях существование должно быть проверил, соединение дает лучше спектакль. В противном случае, вложенные запрос должен быть обработан для каждого результат внешнего запроса, чтобы обеспечить устранение дубликатов. В таком случаях, объединенный подход даст лучшие результаты.

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

Примечание. Хотя в самом вопросе не указан SQL Server 2005, я ответил на это предположение на основе тегов вопроса. Другие механизмы базы данных (даже разные версии SQL Server) могут не оптимизироваться таким же образом.

2 голосов
/ 16 июня 2009

Это разные запросы с разными результатами. С запросом IN вы получите 1 строку из таблицы 'a' всякий раз, когда предикат совпадает. С запросом INNER JOIN вы получите a * b строк всякий раз, когда условие соединения совпадает. Так что со значениями в a {1,2,3} и b в {1,2,2,3} вы получите 1,2,2,3 из JOIN и 1,2,3 из IN.

РЕДАКТИРОВАТЬ - Я думаю, что вы можете встретить здесь несколько ответов, которые дадут вам неправильное представление. Попробуйте сами, и вы увидите, что все эти прекрасные планы запросов:

create table t1 (t1id int primary key clustered)
create table t2 (t2id int identity primary key clustered
    ,t1id int references t1(t1id)
)


insert t1 values (1)
insert t1 values (2)
insert t1 values (3)
insert t1 values (4)
insert t1 values (5)

insert t2 values (1)
insert t2 values (2)
insert t2 values (2)
insert t2 values (3)
insert t2 values (4)


select * from t1 where t1id in (select t1id from t2)
select * from t1 where exists (select 1 from t2 where t2.t1id = t1.t1id)
select t1.* from t1 join t2 on t1.t1id = t2.t1id

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

2 голосов
/ 16 июня 2009

Кроме того, чтобы пойти и протестировать его на большом количестве тестовых данных для себя, я бы сказал, используйте JOINS. У меня всегда была лучшая производительность при использовании их в большинстве случаев по сравнению с подзапросом IN, и у вас есть гораздо больше вариантов настройки, таких как присоединение, что выбрано, что нет, и т. Д.

1 голос
/ 16 июня 2009

Соблюдайте план выполнения для обоих типов и делайте свои выводы. Если количество записей, возвращаемых подзапросом в операторе «IN», не очень мало, вариант IN почти наверняка будет медленнее.

0 голосов
/ 04 июля 2009

Я всегда был сторонником методологии IN. Эта ссылка содержит подробности теста, проведенного в PostgresSQL. http://archives.postgresql.org/pgsql-performance/2005-02/msg00327.php

0 голосов
/ 16 июня 2009

Теория покажет вам только такие вопросы. В конце дня вы захотите протестировать оба запроса и посмотреть, какие из них выполняются быстрее. У меня были случаи, когда версия JOIN занимала более минуты, а версия IN занимала менее секунды. У меня также были случаи, когда JOIN был на самом деле быстрее.

Лично я, как правило, начинаю с версии IN, если знаю, что мне не понадобятся поля из таблицы подзапросов. Если это начнет работать медленно, я буду оптимизировать. К счастью, для больших наборов данных переписывание запроса имеет такое заметное отличие, что вы можете просто отсчитать его с помощью Query Analyzer и узнать, что вы делаете успехи.

Удачи!

...