JEŚLI ISTNIEJE, trwa dłużej niż osadzona instrukcja select


35

Kiedy uruchamiam następujący kod, zajmuje to 22,5 minuty i wykonuje 106 milionów odczytów. Jeśli jednak uruchomię tylko wewnętrzną instrukcję select, zajmuje to tylko 15 sekund i czyta 264k. Na marginesie, wybrane zapytanie nie zwraca żadnych rekordów.

Wiesz, dlaczego IF EXISTSto sprawi, że będzie działało o wiele dłużej i wykona tak wiele innych odczytów? Zmieniłem także zaznaczone polecenie do wykonania SELECT TOP 1 [dlc].[id]i zabiłem je po 2 minutach.

Jako tymczasową poprawkę zmieniłem ją, aby wykonać count (*) i przypisać tę wartość do zmiennej @cnt. Potem robi IF 0 <> @cntoświadczenie. Ale pomyślałem EXISTS, że byłoby lepiej, ponieważ jeśli w instrukcji select zostaną zwrócone rekordy, przestanie wykonywać skanowanie / wyszukiwanie, gdy znajdzie co najmniej jeden rekord, a count(*)zakończy pełne zapytanie. czego mi brakuje?

IF EXISTS
   (SELECT [dlc].[ID]
   FROM TableDLC [dlc]
   JOIN TableD [d]
   ON [d].[ID] = [dlc].[ID]
   JOIN TableC [c]
   ON [c].[ID] = [d].[ID2]
   WHERE [c].[Name] <> [dlc].[Name])
BEGIN
   <do something>
END

4
Aby uniknąć problemu z celem rzędu, innym pomysłem (niesprawdzonym, pamiętajcie o tym!) Może być wypróbowanie odwrotności - IF NOT EXISTS (...) BEGIN END ELSE BEGIN <do something> END.
Aaron Bertrand

Odpowiedzi:


32

Wiesz, dlaczego IF EXISTSto sprawi, że będzie działało o wiele dłużej i wykona tak wiele innych odczytów? Zmieniłem także zaznaczone polecenie do wykonania SELECT TOP 1 [dlc].[id]i zabiłem je po 2 minutach.

Jak wyjaśniłem w mojej odpowiedzi na to powiązane pytanie:

W jaki sposób (i dlaczego) TOP wpływa na plan realizacji?

Użycie EXISTSwprowadza cel wiersza, w którym optymalizator tworzy plan wykonania mający na celu szybkie zlokalizowanie pierwszego wiersza. W ten sposób zakłada się, że dane są równomiernie rozmieszczone. Na przykład, jeśli statystyki pokazują, że jest 100 oczekiwanych dopasowań w 100 000 wierszy, zakładamy, że będzie musiał odczytać tylko 1000 wierszy, aby znaleźć pierwsze dopasowanie.

Spowoduje to wydłużenie czasu realizacji, jeśli założenie to okaże się błędne. Na przykład, jeśli SQL Server wybierze metodę dostępu (np. Skanowanie nieuporządkowane), która zdarzy się zlokalizować pierwszą pasującą wartość bardzo późno podczas wyszukiwania, może to spowodować prawie całkowite skanowanie. Z drugiej strony, jeśli w pierwszych kilku wierszach znajdzie się pasujący wiersz, wydajność będzie bardzo dobra. Jest to podstawowe ryzyko związane z celami rzędu - niespójne wyniki.

Jako tymczasową poprawkę zmieniłem ją, aby wykonać count (*) i przypisać tę wartość do zmiennej

Zazwyczaj możliwe jest przeformułowanie zapytania, tak aby cel wiersza nie został przypisany. Bez celu wiersza zapytanie może zostać zakończone po napotkaniu pierwszego pasującego wiersza (jeśli zostanie poprawnie napisany), ale strategia planu wykonania prawdopodobnie będzie inna (i, mam nadzieję, bardziej skuteczna). Oczywiście count (*) będzie wymagał odczytu wszystkich wierszy, więc nie jest to idealna alternatywa.

Jeśli korzystasz z programu SQL Server 2008 R2 lub nowszego, możesz również ogólnie użyć udokumentowanej i obsługiwanej flagi śledzenia 4138, aby uzyskać plan wykonania bez celu wiersza. Tę flagę można również określić przy użyciu obsługiwanej wskazówki OPTION (QUERYTRACEON 4138) , należy jednak pamiętać, że wymaga ona uprawnień administratora środowiska wykonawczego , chyba że jest używana z przewodnikiem planu.

Niestety

Żadne z powyższych nie działa z IF EXISTSinstrukcją warunkową. Dotyczy to tylko zwykłego DML. To będzie pracować z alternatywnego SELECT TOP (1)sformułowania próbowano. To może być lepsze niż użycie COUNT(*), które musi liczyć wszystkie kwalifikowane wiersze, jak wspomniano wcześniej.

To powiedziawszy, istnieje wiele sposobów wyrażenia tego wymogu, które pozwolą Ci uniknąć celu kontroli wiersza lub go kontrolować, jednocześnie przerywając wyszukiwanie wcześniej. Ostatni przykład:

DECLARE @Exists bit;

SELECT @Exists =
    CASE
        WHEN EXISTS
        (
            SELECT [dlc].[ID]
            FROM TableDLC [dlc]
            JOIN TableD [d]
            ON [d].[ID] = [dlc].[ID]
            JOIN TableC [c]
            ON [c].[ID] = [d].[ID2]
            WHERE [c].[Name] <> [dlc].[Name]
        )
        THEN CONVERT(bit, 1)
        ELSE CONVERT(bit, 0)
    END
OPTION (QUERYTRACEON 4138);

IF @Exists = 1
BEGIN
    ...
END;

Podany przykład alt został wykonany w 3,75 minuty i wykonał 46m odczytów. Tak więc, choć szybsze niż moje pierwotne zapytanie, myślę, że w tym przypadku pozostanę przy @cnt = count (*) i ocenię zmienną później. Zwłaszcza, że ​​99% czasu to działa, nie będzie w tym nic. Wygląda na to, że na podstawie twoich i Roba odpowiedzi, że Exists jest dobry tylko wtedy, gdy naprawdę oczekujesz jakiegoś wyniku, a ten wynik jest równomiernie rozłożony w twoich danych.
Chris Woods,

3
@ChrisWoods: Powiedziałeś „Zwłaszcza, że ​​99% czasu, w którym to działa, nie będzie w tym nic”. To prawie gwarantuje, że cel jednego z rzędów jest złym pomysłem, ponieważ oczekujesz, że zwykle nie będzie żadnych wierszy i będziesz musiał skanować wszystko, aby stwierdzić, że nie ma żadnych. Jeśli nie możesz dodać sprytnego indeksu, trzymaj się COUNT (*).
Ross Presser,

25

Ponieważ EXISTS musi znaleźć tylko jeden wiersz, użyje celu rzędu jednego. Czasami może to spowodować powstanie niezbyt idealnego planu. Jeśli spodziewasz się, że tak będzie dla Ciebie, wypełnij zmienną wynikiem a, COUNT(*)a następnie przetestuj tę zmienną, aby sprawdzić, czy jest większa niż 0.

Więc ... Przy małym celu wiersza pozwoli uniknąć operacji blokowania, takich jak budowanie tabel skrótów lub sortowanie przepływów, które mogą być przydatne do łączenia złączeń, ponieważ zorientuje się, że musi znaleźć coś dość szybko, a zatem zagnieżdżone pętle bądź najlepszy, jeśli coś znajdziesz. Tyle że może to zrobić plan, który jest znacznie gorszy w całym zestawie. Jeśli znalezienie pojedynczego wiersza było szybkie, chciałbyś tę metodę unikania bloków ...

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.