Ten post jest częścią serii artykułów o celach wierszy. Pozostałe części znajdziesz tutaj:
- Część 1:ustalanie i identyfikacja celów wierszy
- Część 2:Częściowe sprzężenia
W tej części omówiono, kiedy i dlaczego optymalizator wprowadza cel wiersza dla anty sprzężenia.
Wprowadzenie
Anti join jest również znane jako anti semi join. Zwraca każdy wiersz z wejścia sprzężenia A, dla którego brak dopasowania można znaleźć na wejściu B.
W przypadku anty sprzężenia:
- Optymalizator może dodaj cel wiersza po wewnętrznej stronie do zastosowania (skorelowane zagnieżdżone pętle łączyć) tylko łączenie anty .
- Cel wiersza nie został dodany dla nieskorelowanych pętli zagnieżdżonych anti join, hash anti join lub merge anti join.
- Jak zawsze, każdy cel wiersza jest dodawany tylko jeśli jest niższy niż oszacowanie bez zastosowania celu w wierszu.
- Nadmiarowe
TOP
po wewnętrznej stronie klauzul iDISTINCT/GROUP BY
operacje można uprościć.
Rozwijając pierwszy punkt powyżej, główną różnicą pomiędzy zastosowaniem semi-join i anty-join row cels jest:
- Zastosuj sprzężenie częściowe zawsze zawiera cel w wierszu (o ile jest to mniej niż szacunek bez celu).
- Zastosuj blokujące sprzężenie może zawierać cel w wierszu , ale tylko wtedy, gdy logiczne sprzężenie przeciw jest przekształcone w aplikację podczas optymalizacji opartej na kosztach .
Przepraszam, że te zasady nie są prostsze, ale ich nie stworzyłem. Mam nadzieję, że niektóre dyskusje i przykłady wyjaśnią to.
Domyślnie brak celu zapobiegającego łączeniu się z wierszami
Optymalizator zakłada, że ludzie piszą semi join (pośrednio np. za pomocą EXISTS
) z oczekiwaniem, że wyszukiwany wiersz zostanie znaleziony . Ustawiono cel dotyczący łączenia częściowego przez optymalizator, aby pomóc w szybkim znalezieniu oczekiwanego pasującego wiersza.
Dla anty dołączenia (wyrażone np. za pomocą NOT EXISTS
) optymalizator zakłada, że pasujący wiersz nie zostanie znaleziony . Cel dotyczący stosowania przeciw sprzężeniu nie jest ustawiony przez optymalizator, ponieważ oczekuje, że będzie musiał sprawdzić wszystkie wiersze, aby potwierdzić brak dopasowania.
Jeśli okaże się, że istnieje pasujący wiersz, zastosowanie sprzężenia anty może zająć więcej czasu, aby zlokalizować ten wiersz, niż gdyby użyto celu wiersza. Niemniej jednak anti join nadal zakończy wyszukiwanie, gdy tylko zostanie napotkane (nieoczekiwane) dopasowanie.
Zastosuj warunki celu zapobiegające łączeniu się z wierszami
SQL Server nie zapewnia nam możliwości bezpośredniego napisania anti join, więc musimy użyć obejść, takich jak NOT EXISTS
, NOT IN/ANY/SOME
lub EXCEPT
. Każda z tych form skutkuje reprezentacją podzapytania w przeanalizowanym drzewie na początku kompilacji zapytania. To podzapytanie jest zawsze rozwijane w zastosowanie, a następnie przekształcane w logiczne sprzężenie przeciw tam, gdzie to możliwe (szczegóły są takie same jak w przypadku semi-join omówionego w części 2). To wszystko dzieje się, zanim nawet błahy plan zostanie rozważony.
Aby anty sprzężenie otrzymało cel rzędu, musi wpisać optymalizacja oparta na kosztach jako logiczne przeciwdziałanie sprzężeniu (co oznacza, że transformacja z zastosowania powyżej musiała się powieść). Następnie optymalizator oparty na kosztach musi zdecydować się na zaimplementowanie logicznego sprzężenia anty jako zastosuj . Aby tak się stało, optymalizator musi najpierw wybrać eksplorację opcja zastosowania; to musi wybrać to najtańsza opcja (dla tej części planu).
Cel wiersza zapobiegający sprzężeniu jest ustalany przez dowolną z reguł optymalizacji opartych na kosztach, które mogą przekształcić sprzężenie w zastosowanie. Anty sprzężenie, które wchodzi optymalizacja oparta na kosztach jako zastosowanie (ponieważ nie powiodło się przekształcenie w logiczne anti join) nie mają zastosowany cel wiersza.
Optymalizator oparty na kosztach zbada i wybierze opcję „połącz, aby zastosować” tylko wtedy, gdy istnieje skuteczny sposób na znalezienie pasujących wewnętrznych wierszy bocznych (np. za pomocą indeksu). Optymalizator nie zbada tej opcji, jeśli wewnętrzne poddrzewo join nie ma niczego przydatnego dla predykatu Apply dla "zatrzasku do". Może to być indeks, indeks tymczasowy (za pośrednictwem szybkiego buforowania indeksu) lub inny klucz logiczny. Dodanie celu dotyczącego wiersza pomaga optymalizatorowi oszacować koszt opcji dołączenia w celu zastosowania, biorąc pod uwagę, że należy zlokalizować maksymalnie jeden wiersz.
Należy zauważyć, że zastosowanie anty sprzężenia może pojawić się w planie wykonania bez celu wiersza. Dzieje się tak, gdy początkowa transformacja z zastosowania na sprzężenie nie powiedzie się, co jest stosunkowo powszechne. Kiedy tak się dzieje, anty sprzężenie zaczyna działać w optymalizatorze opartym na kosztach jako zastosowanie, a więc nigdy nie ma celu związanego z wierszem przez jedną z reguł łączenia z zastosowaniem.
Oczywiście cel w wierszu może być również wprowadzony po wewnętrznej stronie tego zastosowania za pomocą innego mechanizmu (niepowiązanego z zastosowaniem), na przykład przez oddzielny operator Top.
Podsumowując:
- Anty sprzężenia może uzyskać tylko cel wiersza podczas optymalizacji opartej na kosztach (CBO).
- Reguły tłumaczące sprzężenie anty na zastosowanie dodają cel wiersza.
- Anty sprzężenia musi wprowadzić CBO jako sprzężenie, a nie zastosowanie.
- Aby wprowadzić CBO jako złączenie, wcześniejsze fazy muszą mieć możliwość przepisania podzapytania jako złączenia (poprzez etap stosowania).
- CBO bada połączenie tylko w celu zastosowania transformacji w obiecujących przypadkach.
Przykład
Trochę trudniej jest zademonstrować to wszystko dla zastosowania anti join niż w przypadku zastosowania semi join. Powody tego zostaną omówione w części 4.
Tymczasem oto przykład AdventureWorks pokazujący, jak powstaje zastosowanie sprzężenia anty z celem wiersza, przy użyciu tych samych nieudokumentowanych flag śledzenia, co w przypadku sprzężenia pół. Dodano flagę śledzenia 8608, aby pokazać początkową strukturę notatki na początku optymalizacji opartej na kosztach.
SELECT P.ProductID FROM Production.Product AS P WHERE NOT EXISTS ( SELECT 1 FROM Production.TransactionHistoryArchive AS THA WHERE THA.ProductID = P.ProductID UNION ALL SELECT 1 FROM Production.TransactionHistory AS TH WHERE TH.ProductID = P.ProductID ) OPTION (QUERYTRACEON 3604, QUERYTRACEON 8607, QUERYTRACEON 8608, QUERYTRACEON 8612, QUERYTRACEON 8621);
Istniejące podzapytanie jest najpierw przekształcane w zastosowanie: