2013-06-14 11 views
17

Mam tabelę (MainTable) z nieco ponad 600 000 rekordów. Łączy na siebie poprzez 2nd tabeli (JoinTable) w relacji typu rodzic/dziecko:LEFT JOIN Znacząco szybszy niż INNER DOŁĄCZ

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 

Wiem, że każde dziecko ma rekord rekord nadrzędny a dane w JoinTable jest acurate.

Po uruchomieniu tego zapytania trwa dosłownie kilka minut. Jednak jeśli mogę dołączyć do dominującej przy użyciu LEFT JOIN następnie zajmuje < 1 sekundę, aby uruchomić:

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]... 

Rozumiem różnicę między wynikami w INNER JOIN i LEFT JOIN. W tym przypadku zwraca dokładnie taki sam wynik, jak każde dziecko ma rodzica. Jeśli pozwolę obu zadaniom uruchomić, mogę porównać zestawy danych i są one dokładnie takie same.

Dlaczego to LEFT JOIN działa o wiele szybciej niż INNER JOIN?


UPDATE sprawdzone plany zapytań i przy użyciu sprzężenie wewnętrzne zaczyna się w zbiorze dominującej. Podczas łączenia lewego zaczyna się od zbioru danych podrzędnego.

Indeksy, których używa, są takie same.

Czy mogę zmusić go, aby zawsze zaczynał od dziecka? Używanie lewego sprzężenia działa, po prostu wydaje się nie tak.


Podobne pytania zadawano wcześniej, ale nikt nie odpowiedział na moje pytanie.

np. wybrana odpowiedź w INNER JOIN vs LEFT JOIN performance in SQL Server mówi, że lewe połączenia są zawsze wolniejsze niż wewnętrzne połączenia. Argument ma sens, ale to nie jest to, co widzę.

+1

Sprawdź plany. – Blorgbeard

+0

@Blogbeard - patrz aktualizacja – Greg

Odpowiedz

12

Lewica dołączyć wydaje się szybciej ponieważ SQL jest zmuszony zrobić mniejszy wybór, a następnie przystąpić do ten mniejszy zestaw rekordów. Z jakiegoś powodu optymalizator nie chce tego robić naturalnie.

3 sposoby, aby zmusić dołącza się zdarzyć w odpowiedniej kolejności:

  1. wybrać pierwszy podzbiór danych do (lub zmiennej tabeli) tymczasowe tabeli następnie przystąpić na to
  2. Użyj lewej dołącza (i pamiętaj, że może to zwrócić różne dane, ponieważ jest to lewe połączenie, a nie wewnętrzne sprzężenie).
  3. użyj słowa kluczowego FORCE ORDER. Zwróć uwagę, że jeśli rozmiary tabeli lub schematy ulegną zmianie, plan zapytania może nie być poprawny (patrz https://dba.stackexchange.com/questions/45388/forcing-join-order).
+1

Wiem, że to trochę za późno, ale możesz chcieć upewnić się, że statystyki bazy danych są aktualne. Jeśli optymalizator zapytań nie wie o względnych rozmiarach tabel i dystrybucjach wartości w kolumnach łączenia, może podjąć decyzję o * złym * planie zapytania (SQL Server ma najbardziej optymistyczny optymalizator zapytań dla wszystkich baz danych, z którymi pracuję) . Oto wpis na blogu na ten temat: http://blogs.msdn.com/b/buckwoody/archive/2009/08/18/sql-server-best-practices-auto-create-and-auto-update-statistics- powinien-być-w-czasie-time.aspx – Curt

+0

Dzięki za pomysł. sprawdziliśmy statystyki i były aktualne – Greg

+0

Wiem, że jest późno, ale może pomóc komuś innemu. Jedynym punktem, z którym nie zgadzam się, jest sugestia dotycząca zmiennej Table. Zmienne tabeli zawsze zwracają szacunkową liczbę wierszy równą 1, niezależnie od tego, ile wierszy znajduje się w tabeli. Może to ogromnie zniekształcić ten plan. Przeczytaj ten 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 jednak w 2012 r. w dodatku SP2 znajduje się znacznik śledzenia, który może pomóc –

2

Wypróbuj tę. Taki sam wynik, inne podejście:

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 

Jeśli to nie pomoże, użyj CTE:

;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

Wydawało się, że CTE nie pomogło, ale zmusiło go do zmiany w tabeli. Jeśli nie pojawią się żadne inne odpowiedzi, ucieknę z tym. – Greg