Jak zoptymalizować zapytanie T-SQL za pomocą planu wykonania

15

Mam zapytanie SQL, które spędziłem dwa ostatnie dni, próbując zoptymalizować za pomocą prób i błędów i planu wykonania, ale bezskutecznie. Proszę wybacz mi to, ale opublikuję tutaj cały plan wykonania. Dołożyłem starań, aby nazwy tabel i kolumn w zapytaniu i planie wykonania były ogólne, zarówno dla zwięzłości, jak i dla ochrony adresu IP mojej firmy. Plan wykonania można otworzyć za pomocą SQL Sentry Plan Explorer .

Zrobiłem sporo T-SQL, ale korzystanie z planów wykonania w celu optymalizacji mojego zapytania jest dla mnie nowym obszarem i naprawdę starałem się zrozumieć, jak to zrobić. Tak więc, jeśli ktoś mógłby mi w tym pomóc i wyjaśnić, w jaki sposób można rozszyfrować ten plan wykonania, aby znaleźć w zapytaniu sposoby na jego optymalizację, byłbym na zawsze wdzięczny. Muszę zoptymalizować jeszcze wiele zapytań - potrzebuję tylko odskoczni, aby pomóc mi w tym pierwszym.

To jest zapytanie:

DECLARE @Param0 DATETIME     = '2013-07-29';
DECLARE @Param1 INT          = CONVERT(INT, CONVERT(VARCHAR, @Param0, 112))
DECLARE @Param2 VARCHAR(50)  = 'ABC';
DECLARE @Param3 VARCHAR(100) = 'DEF';
DECLARE @Param4 VARCHAR(50)  = 'XYZ';
DECLARE @Param5 VARCHAR(100) = NULL;
DECLARE @Param6 VARCHAR(50)  = 'Text3';

SET NOCOUNT ON

DECLARE @MyTableVar TABLE
(
    B_Var1_PK int,
    Job_Var1 varchar(512),
    Job_Var2 varchar(50)
)

INSERT INTO @MyTableVar (B_Var1_PK, Job_Var1, Job_Var2) 
SELECT B_Var1_PK, Job_Var1, Job_Var2 FROM [fn_GetJobs] (@Param1, @Param2, @Param3, @Param4, @Param6);

CREATE TABLE #TempTable
(
    TTVar1_PK INT PRIMARY KEY,
    TTVar2_LK VARCHAR(100),
    TTVar3_LK VARCHAR(50),
    TTVar4_LK INT,
    TTVar5 VARCHAR(20)
);

INSERT INTO #TempTable
SELECT DISTINCT
    T.T1_PK,
    T.T1_Var1_LK,
    T.T1_Var2_LK,
    MAX(T.T1_Var3_LK),
    T.T1_Var4_LK
FROM
    MyTable1 T
    INNER JOIN feeds.MyTable2 A ON A.T2_Var1 = T.T1_Var4_LK
    INNER JOIN @MyTableVar B ON B.Job_Var2 = A.T2_Var2 AND B.Job_Var1 = A.T2_Var3
GROUP BY T.T1_PK, T.T1_Var1_LK, T.T1_Var2_LK, T.T1_Var4_LK

-- This is the slow statement...
SELECT 
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
        WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
        WHEN 'Text3' THEN T.TTVar2_LK
    END,
    T.TTVar4_LK,
    T.TTVar3_LK,
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN F.F_Var1
        WHEN 'Text2' THEN F.F_Var2
        WHEN 'Text3' THEN T.TTVar5
    END,
    A.A_Var3_FK_LK,
    C.C_Var1_PK,
    SUM(CONVERT(DECIMAL(18,4), A.A_Var1) + CONVERT(DECIMAL(18,4), A.A_Var2))
FROM #TempTable T
    INNER JOIN TableA (NOLOCK) A ON A.A_Var4_FK_LK  = T.TTVar1_PK
    INNER JOIN @MyTableVar     B ON B.B_Var1_PK     = A.Job
    INNER JOIN TableC (NOLOCK) C ON C.C_Var2_PK     = A.A_Var5_FK_LK
    INNER JOIN TableD (NOLOCK) D ON D.D_Var1_PK     = A.A_Var6_FK_LK
    INNER JOIN TableE (NOLOCK) E ON E.E_Var1_PK     = A.A_Var7_FK_LK  
    LEFT OUTER JOIN feeds.TableF (NOLOCK) F ON F.F_Var1 = T.TTVar5
WHERE A.A_Var8_FK_LK = @Param1
GROUP BY
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
        WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
        WHEN 'Text3' THEN T.TTVar2_LK
    END,
    T.TTVar4_LK,
    T.TTVar3_LK,
    CASE E.E_Var1_LK 
        WHEN 'Text1' THEN F.F_Var1
        WHEN 'Text2' THEN F.F_Var2
        WHEN 'Text3' THEN T.TTVar5
    END,
    A.A_Var3_FK_LK, 
    C.C_Var1_PK


IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
    DROP TABLE #TempTable
END
IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
    DROP TABLE #TempTable
END

Odkryłem, że trzecie stwierdzenie (skomentowane jako wolne) jest częścią, która zajmuje najwięcej czasu. Dwa oświadczenia przed powrotem wracają niemal natychmiast.

Plan wykonania jest dostępny jako XML pod tym linkiem .

Lepiej kliknąć prawym przyciskiem myszy i zapisać, a następnie otworzyć w SQL Sentry Plan Explorer lub innym oprogramowaniu do przeglądania zamiast otwierać w przeglądarce.

Jeśli potrzebujesz więcej informacji o tabelach lub danych, nie wahaj się zapytać.

Neo
źródło
2
Twoje statystyki są dalekie. Kiedy po raz ostatni rozpakowałeś indeksy lub zaktualizowałeś statystyki? Ponadto spróbowałbym użyć tabeli temp zamiast zmiennej tabeli @MyTableVar, ponieważ optymalizator naprawdę nie może używać statystyk zmiennych tabeli.
Adam Haines,
Dziękuję za odpowiedź Adam. Zmiana @MyTableVar na tabelę tymczasową nie ma żadnego efektu, ale jest to tylko niewielka liczba wierszy (co można zobaczyć w planie wykonania). Co w planie wykonania pokazuje, że moje statystyki są dalekie? Czy wskazuje, które indeksy powinny zostać zreorganizowane lub przebudowane, a które tabele powinny zaktualizować statystyki?
Neo,
3
Łączenie mieszające w prawym dolnym rogu ma szacunkowo 24 000 wierszy w danych wejściowych kompilacji, ale rzeczywiste 3265620, więc może się rozlewać tempdb. tzn. oszacowania dla wierszy wynikających z połączenia między TableAi @MyTableVarsą dalekie. Również liczba wierszy wchodzących w sortowanie jest znacznie większa niż szacowana, więc mogą się również rozlewać.
Martin Smith

Odpowiedzi:

22

Zanim przejdziesz do głównej odpowiedzi, musisz zaktualizować dwa programy.

Wymagane aktualizacje oprogramowania

Pierwszym z nich jest SQL Server. Używasz programu SQL Server 2008 z dodatkiem Service Pack 1 (kompilacja 2531). Należy załatać co najmniej bieżący dodatek Service Pack (SQL Server 2008 Service Pack 3 - kompilacja 5500). Najnowszą kompilacją SQL Server 2008 w momencie pisania jest Service Pack 3, aktualizacja zbiorcza 12 (kompilacja 5844).

Drugim oprogramowaniem jest SQL Sentry Plan Explorer . Najnowsze wersje mają znaczące nowe funkcje i poprawki, w tym możliwość bezpośredniego przesłania planu zapytań do analizy eksperckiej (nie trzeba nigdzie wklejać XML!)

Analiza planu zapytań

Szacunek liczności dla zmiennej tabeli jest dokładnie poprawny, dzięki ponownej kompilacji na poziomie instrukcji:

oszacowanie zmiennej tabeli

Niestety zmienne tabeli nie utrzymują statystyk dystrybucji, więc optymalizator wie tylko, że istnieje sześć wierszy; nic nie wie o wartościach, które mogą znajdować się w tych sześciu rzędach. Ta informacja jest kluczowa, biorąc pod uwagę, że następną operacją jest dołączenie do innej tabeli. Szacunek liczności na podstawie tego sprzężenia opiera się na dzikim przypuszczeniu optymalizatora:

najpierw dołącz oszacowanie

Od tego momentu plan wybrany przez optymalizator opiera się na niepoprawnych informacjach, więc nic dziwnego, że wydajność jest tak niska. W szczególności pamięć odłożona na sortowanie i tabele skrótów dla połączeń skrótów będą zdecydowanie za małe. W czasie wykonywania przepełnione operacje sortowania i mieszania zostaną rozlane na fizyczny dysk tempdb.

SQL Server 2008 nie podkreśla tego w planach wykonania; możesz monitorować wycieki za pomocą Rozszerzonych zdarzeń lub Ostrzeżeń sortowania Profiler i Ostrzeżeń mieszania . Pamięć jest zarezerwowana dla sortowania i skrótów na podstawie oszacowań liczności przed rozpoczęciem wykonywania i nie można jej zwiększyć podczas wykonywania, niezależnie od ilości wolnej pamięci, jaką może mieć Twój SQL Server. Dokładne oszacowanie liczby wierszy jest zatem kluczowe dla każdego planu wykonania, który wymaga operacji zajmujących pamięć obszaru roboczego.

Twoje zapytanie jest również sparametryzowane. Należy rozważyć dodanie OPTION (RECOMPILE)do zapytania, jeśli różne wartości parametrów wpływają na plan zapytania. Prawdopodobnie powinieneś rozważyć użycie go, aby optymalizator mógł zobaczyć wartość @Param1w czasie kompilacji. Jeśli nic więcej, może to pomóc optymalizatorowi uzyskać bardziej rozsądne oszacowanie dla wyszukiwania indeksu pokazanego powyżej, biorąc pod uwagę, że tabela jest bardzo duża i podzielona na partycje. Może także umożliwić statyczną eliminację partycji.

Spróbuj ponownie wykonać zapytanie z tymczasową tabelą zamiast zmiennej tabeli i OPTION (RECOMPILE) . Powinieneś także spróbować zmaterializować wynik pierwszego łączenia w innej tabeli tymczasowej i uruchomić w tym celu resztę zapytania. Liczba rzędów nie jest aż tak duża (3 285 620), więc powinno to być dość szybkie. Optymalizator będzie wtedy miał dokładne oszacowanie liczności i statystyki rozkładu dla wyniku połączenia. Przy odrobinie szczęścia reszta planu ładnie się ułoży.

W oparciu o właściwości pokazane w planie zapytanie materializujące wyglądałoby następująco:

SELECT
    A.A_Var7_FK_LK,
    A.A_Var4_FK_LK,
    A.A_Var6_FK_LK, 
    A.A_Var5_FK_LK,
    A.A_Var1,
    A.A_Var2,
    A.A_Var3_FK_LK
INTO #AnotherTempTable
FROM @MyTableVar AS B
JOIN TableA AS A
    ON A.Job = B.B_Var1_PK
WHERE
    A_Var8_FK_LK = @Param1;

Możesz również INSERTprzejść do predefiniowanej tabeli tymczasowej (prawidłowe typy danych nie są pokazane w planie, więc nie mogę wykonać tej części). Nowa tabela tymczasowa może, ale nie musi, korzystać z indeksów klastrowych i nieklastrowanych.

Paul White 9
źródło
Dziękuję bardzo za tę szczegółową odpowiedź. Przepraszam, że odpowiedź zajęła tydzień - pracowałem nad tym każdego dnia, przeplatając się z innymi pracami. Wdrożyłem twoje sugestie materializujące przyłączenie do TableA do #AnotherTempTable. Wydawało się, że ma to najlepszy wpływ - inne sugestie (użycie tabeli tymczasowej zamiast zmiennej tabeli dla @MyTableVar i użycie OPTION (RECOMPILE)nie przyniosło żadnego efektu ani żadnego. „Anonimizacja” i „Wyślij do SQLPerformance.com” opcje w SQL Sentry Plan Explorer są świetne - właśnie ich użyłem: answer.sqlperformance.com/questions/1087
Neo
-6

Zauważyłem, że na @MyTableVar powinna znajdować się PK i zgadzam się, że #MyTableVar często lepiej działa (szczególnie przy większej liczbie wierszy).

Warunek w ramach klauzuli where

   WHERE A.A_Var8_FK_LK = @Param1

powinien zostać przeniesiony do wewnętrznego połączenia A AND'ed. Z mojego doświadczenia optymalizator nie jest wystarczająco sprytny (przepraszam, że nie spojrzałem na plan) i może mieć ogromną różnicę.

Jeśli te zmiany nie wykazują poprawy, stworzyłbym następną tabelę tymczasową A i wszystkie rzeczy, które łączy z ograniczonym (ładnie?) Przez A.A_Var8_FK_LK = @ Param1, jeśli takie grupowanie ma dla ciebie logiczny sens.

Następnie utwórz indeks klastrowy w tej tabeli tymczasowej (przed lub po utworzeniu) dla następnego warunku łączenia.

Następnie dołącz ten wynik do kilku pozostałych tabel (F i T).

Bam, który potrzebuje śmierdzącego planu zapytań, gdy szacunki wierszy są wyłączone, a czasem i tak nie można ich łatwo poprawić ). Zakładam, że masz odpowiednie wskaźniki, które najpierw sprawdzę w ramach planu.

Ślad może pokazywać wycieki tempdb, które mogą, ale nie muszą mieć drastycznego wpływu.

Innym alternatywnym podejściem - co najmniej szybszym do wypróbowania - jest uporządkowanie tabel od najmniejszej liczby wierszy (A) do najwyższej, a następnie rozpoczęcie dodawania scalania, mieszania i pętli do złączeń. Gdy podpowiedzi są obecne, kolejność łączenia jest ustalona zgodnie z opisem. Inni użytkownicy mądrze unikają tego podejścia, ponieważ może zaszkodzić na dłuższą metę, jeśli względna liczba wierszy zmieni się dramatycznie. Pożądana jest minimalna liczba wskazówek.

Jeśli robisz wiele z nich, być może komercyjny optymalizator jest wart wypróbowania (lub wersji próbnej) i nadal jest dobrym doświadczeniem edukacyjnym.

Crokusek
źródło
Tak to jest. Zapewnia, że ​​wiersze zwrócone przez A są ograniczone przez ograniczenie. W przeciwnym razie optymalizator może dołączyć najpierw, a później zastosować ograniczenie. Zajmuję się tym codziennie.
crokusek
4
@crokusek Po prostu się mylisz. Optymalizator SQL-Server jest całkiem niezły, wiedząc, że zapytania są równoważne (niezależnie od tego, czy warunek znajduje się w klauzuli WHERE, czy ON), gdy jest to połączenie INNER.
ypercubeᵀᴹ
6
Przydatna może być seria Paula White'a w Optymalizatorze zapytań .
Martin Smith
To okropny nawyk. Może tak będzie w tym konkretnym przypadku (gdzie jest jedno ograniczenie), ale pochodzę z krainy wielu programistów stosujących AND na warunkach klauzuli where. SQL Server działa nie konsekwentnie „przenieść” je z powrotem do złączenia dla Ciebie.
crokusek
Zgadzam się niepoprawnie dla połączeń zewnętrznych (i prawych). Ale kiedy w klauzuli where występują tylko wyrażenia AND, a każdy termin odpowiada wyłącznie konkretnemu sprzężeniu wewnętrznemu, termin ten można bezpiecznie i pewnie przenieść do lokalizacji „on” jako optymalizację i najlepszą praktykę (imo). To, czy jest to „prawdziwy” warunek łączenia, czy tylko stałe ograniczenie, jest drugorzędne w stosunku do dużego wzrostu wydajności. Ten link dotyczy trywialnej sprawy. W prawdziwym życiu istnieje wiele warunków, w których występują funkcje konwersji () i matematyki, dzięki czemu są lepszymi kandydatami do uzyskiwania najlepszych praktyk.
crokusek