2013-06-14 3 views
17

У меня есть таблица (MainTable) с чуть более 600 000 записей. Он присоединяется на себя через 2 таблицы (JoinTable) в отношениях типа родитель/ребенок:LEFT JOIN Значительно быстрее, чем INNER JOIN

SELECT Child.ID, Parent.ID 
FROM  MainTable 
AS  Child 
JOIN  JoinTable 
     ON Child.ID = JoinTable.ID 
JOIN  MainTable 
AS  Parent 
     ON Parent.ID = JoinTable.ParentID 
    AND Parent.SomeOtherData = Child.SomeOtherData 

Я знаю, что каждый ребенок имеет запись родительской записи, и данные в JoinTable является Acurate.

Когда я запускаю этот запрос, требуется буквально несколько минут для запуска. Однако, если я присоединюсь к Родителю с помощью левого Присоединяйтесь тогда он принимает < 1 секунду для запуска:

SELECT Child.ID, Parent.ID 
FROM  MainTable 
AS  Child 
JOIN  JoinTable 
     ON Child.ID = JoinTable.ID 
LEFT JOIN MainTable 
AS  Parent 
     ON Parent.ID = JoinTable.ParentID 
    AND Parent.SomeOtherData = Child.SomeOtherData 
WHERE ...[some info to make sure we don't select parent records in the child dataset]... 

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

Почему LEFT JOIN работает намного быстрее, чем INNER JOIN?


UPDATE Проверил планы запросов и при использовании внутреннего соединения начинается с Материнской набора данных. При выполнении левого соединения начинается с набора данных child.

Индексы, которые он использует, все одинаковы.

Могу ли я заставить его всегда начинать с ребенка? Использование левого соединения работает, он просто чувствует себя не так.


Подобные вопросы заданы здесь раньше, но никто, кажется, не отвечает на мой вопрос.

например. выбранный ответ в INNER JOIN vs LEFT JOIN performance in SQL Server говорит, что левые соединения всегда медленнее, чем внутренние. Аргумент имеет смысл, но это не то, что я вижу.

+1

Проверьте планы. – Blorgbeard

+0

@Blogbeard - см. Обновление – Greg

ответ

12

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

3 способа заставить присоединяется произойти в правильном порядке:

  1. Выберите первое подмножество данных во временную таблицу (или переменной таблицы), а затем присоединиться на ней
  2. Используйте левую присоединяется (и помните, что это может возвращать разные данные, потому что это левое соединение, а не внутреннее соединение).
  3. используйте ключевое слово FORCE ORDER. Обратите внимание, что при изменении размеров таблиц или схем план запроса может быть неправильным (см. https://dba.stackexchange.com/questions/45388/forcing-join-order)
+1

Я знаю, что это немного поздно, но вы можете убедиться, что статистика базы данных обновлена. Если оптимизатор запросов не знает об относительных размерах таблиц и распределениях значений в столбцах соединения, он может принять решение плана * * * плана запроса (у SQL Server есть наиболее стабильно хороший оптимизатор запросов для всех баз данных, с которыми я работаю) , Вот запись в блоге по теме: http://blogs.msdn.com/b/buckwoody/archive/2009/08/18/sql-server-best-practices-auto-create-and-auto-update-statistics- должно быть на самом-самом-времени .aspx – Curt

+0

Спасибо за идею. мы проверили статистику, и они были обновлены – Greg

+0

Я знаю его опоздание, но может помочь кому-то еще. Единственное, с чем я не согласен, это предложение таблицы. Переменные таблицы всегда возвращают оцененное количество строк в 1 независимо от количества строк в таблице. Это может сильно исказить план. Прочтите это http://blogs.msdn.com/b/psssql/archive/2014/08/11/if-you-have-queries-that-use-table-variables-sql-server-2012-sp2-can- help.aspx, однако в 2012 году в пакете SP2 есть traceflag, который может помочь –

2

Попробуйте этот. Тот же результат, другой подход:

SELECT c.ID, p.ID 
FROM 
(SELECT Child.ID, JoinTable.ParentID 
FROM  MainTable 
AS  Child 
JOIN  JoinTable 
     ON Child.ID = JoinTable.ID) AS c 
INNER JOIN 
(SELECT Parent.ID, JoinTable.ID 
FROM  MainTable 
AS  Parent 
JOIN  JoinTable 
     ON Parent.ID = JoinTable.ParentID 
    AND Parent.SomeOtherData = Child.SomeOtherData) AS p 
ON c.ParentID = p.ID 

Если это не помогает, используйте КТР:

;WITH cte AS 
(SELECT Child.ID, JoinTable.ParentID 
FROM  MainTable 
AS  Child 
JOIN  JoinTable 
     ON Child.ID = JoinTable.ID) 
SELECT cte.ID, Parent.ID 
FROM cte INNER JOIN 
MainTable 
AS  Parent 
     ON Parent.ID = cte.ParentID 
    AND Parent.SomeOtherData = cte.SomeOtherData 
+0

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

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