Mam poniższe zapytanie:
select databasename
from somedb.dbo.bigtable l where databasename ='someval' and source <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source)
Powyższe zapytanie kończy się w ciągu trzech sekund.
Jeśli powyższe zapytanie zwraca jakąkolwiek wartość, chcemy, aby procedura przechowywana zakończyła się, więc przepisałem ją jak poniżej:
If Exists(
select databasename
from somedb.dbo.bigtable l where databasename ='someval' and source <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source)
)
Begin
Raiserror('Source missing',16,1)
Return
End
Jednak zajmuje to 10 minut.
Mogę przepisać powyższe zapytanie, jak poniżej, które również kończy się w mniej niż 3 sekundy:
select databasename
from somedb.dbo.bigtable l where databasename ='someval' and source <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source
if @@rowcount >0
Begin
Raiserror('Source missing',16,1)
Return
End
Problem z powyższym przepisem polega na tym, że powyższe zapytanie jest częścią większej procedury składowanej i zwraca wiele zestawów wyników. W języku C # iterujemy każdy zestaw wyników i wykonujemy pewne przetwarzanie.
Powyższe zwraca pusty zestaw wyników, więc jeśli pójdę z tym podejściem, muszę zmienić mój C # i ponownie wdrożyć.
Więc moje pytanie brzmi:
dlaczego używanie tylko
IF EXISTS
zmienia plan, który zajmuje tak dużo czasu?
Poniżej znajdują się szczegółowe informacje, które mogą ci pomóc i dać mi znać, jeśli potrzebujesz jakichkolwiek szczegółów:
- Utwórz skrypt tabeli i statystyk, aby uzyskać taki sam plan jak mój
- Plan powolnej realizacji
Plan szybkiej realizacji
Powolny plan za pomocą Brentozar Wklej plan
Szybki plan za pomocą Brentozar Wklej plan
Uwaga: oba zapytania są takie same (przy użyciu parametrów), jedyna różnica polega na EXISTS
(mogłem popełnić błędy podczas anonimizacji).
Skrypty tworzenia tabel znajdują się poniżej:
http://pastebin.com/CgSHeqXc - statystyki małego stołu
http://pastebin.com/GUu9KfpS - statystyki dużego stołu
źródło
Odpowiedzi:
Jak zostało wyjaśnione przez Pawła Białej w swoim blogu: Wewnątrz Optimizer Gole Row dogłębnie te
EXISTS
wprowadza cel rząd, który preferujeNESTED LOOPS
lubMERGE JOIN
ponadHASH MATCH
W twoim zapytaniu najwyraźniej dzieje się tak, gdy wprowadza się zagnieżdżone pętle i usuwa równoległość, co powoduje wolniejszy plan.
Prawdopodobnie będziesz musiał znaleźć sposób na przepisanie zapytania bez korzystania z polecenia
NOT EXISTS
z zapytania.Możesz uniknąć przepisania zapytania za pomocą
LEFT OUTER JOIN
i sprawdzenia, czy nie ma wiersza w małej tabeli, testującNULL
Prawdopodobnie możesz również użyć
EXCEPT
zapytania, w zależności od liczby pól, które musisz porównać w następujący sposób:Pamiętaj, że Aaron Bertrand ma post na blogu, podający powody, dla których woli NIE ISTNIEĆ, które powinieneś przeczytać, aby sprawdzić, czy inne podejścia działają lepiej, i być świadomym potencjalnych problemów z poprawnością w przypadku wartości NULL.
Powiązane pytania i odpowiedzi: JEŚLI ISTNIEJE, trwa dłużej niż osadzona instrukcja select
źródło
Musisz przepisać zapytanie, używając jawnych połączeń i określić, jakiej operacji łączenia chcesz użyć (pętla, skrót lub scalanie) w ten sposób.
Podczas korzystania z EXISTS lub NOT EXISTS SQL Server wygenerował plan zapytań z operacją NESTED LOOP, zakładając, że powinien on przechodzić przez wszystkie wiersze w zestawie jeden po drugim, szukając pierwszego wiersza, aby spełnić warunek. Użycie HASH JOIN przyspieszy.
źródło
Natknąłem się na ten sam problem, udało mi się obejść, unikając używania „ISTNIEJĄCEGO” i używając funkcji „COUNT ()” i instrukcji „JEŚLI ... ELSE”.
Na przykład spróbuj wykonać następujące czynności:
Powodem dodawania „+ 1” do liczby jest to, że mogę użyć „> 1” w warunku JEŻELI, użycie „> 0” lub „<> 0” spowoduje, że zapytanie użyje zagnieżdżonych pętli zamiast skrótu Mecz. Nie zastanawiałem się, dlaczego tak się dzieje, byłoby interesujące dowiedzieć się, dlaczego.
Mam nadzieję, że to pomaga!
źródło