Parametr Sniffing vs VARIABLES vs Recompile vs OPTIMIZE FOR UNKNOWN

40

Tak więc mieliśmy rano długi proces powodujący problemy (30 sekund + czas działania). Postanowiliśmy sprawdzić, czy winą jest wąchanie parametrów. Tak więc przepisaliśmy proc i zmieniliśmy parametry wejściowe na zmienne, aby wyeliminować wąchanie parametrów. Sprawdzone / prawdziwe podejście. Bam, poprawiony czas zapytania (mniej niż 1 sekunda). Podczas przeglądania planu zapytań znaleziono ulepszenia w indeksie, z którego nie korzystał oryginał.

Aby sprawdzić, czy nie otrzymaliśmy fałszywego wyniku pozytywnego, wykonaliśmy darmową procedurę dbcccache dbcc na oryginalnym proc i ponownie sprawdziliśmy, czy poprawione wyniki będą takie same. Ku naszemu zdziwieniu oryginalny proces wciąż działał powoli. Próbowaliśmy ponownie Z Z RECOMPILEM, wciąż powolnym (próbowaliśmy ponownej kompilacji po wywołaniu proc i samemu proc). Zrestartowaliśmy nawet serwer (oczywiście dev box).

Moje pytanie brzmi więc ... jak można obwiniać wąchanie parametrów, gdy otrzymujemy to samo wolne zapytanie na pustej pamięci podręcznej planu ... nie powinno być żadnych parametrów do wąchania ???

Czy zamiast tego wpływają na nas statystyki tabeli niezwiązane z pamięcią podręczną planu. A jeśli tak, to dlaczego ustawienie przychodzących parametrów na zmienne pomogłoby?

W dalszych testach odkryliśmy również, że wstawienie OPCJI (OPTYMALIZACJA NIEZNANE) na wewnętrznych elementach proc DID zapewnia oczekiwany ulepszony plan.

Więc, niektórzy z was mądrzejsi ode mnie, czy możecie dać jakieś wskazówki, co się dzieje za kulisami, aby uzyskać tego rodzaju wynik?

Z drugiej strony, powolny plan również zostaje wcześnie przerwany z uzasadnieniem, GoodEnoughPlanFoundpodczas gdy szybki plan nie ma powodu wcześniejszego przerwania w rzeczywistym planie.

W podsumowaniu

  • Tworzenie zmiennych na podstawie parametrów przychodzących (1 sekunda)
  • z rekompilacją (30+ sekund)
  • dbcc freeproccache (30+ sec)
  • OPCJA (OPTYMALIZACJA DLA UKNOWN) (1 s)

AKTUALIZACJA:

Zobacz plan powolnego wykonywania tutaj: https://www.dropbox.com/s/cmx2lrsea8q8mr6/plan_slow.xml

Zobacz plan szybkiego wykonania tutaj: https://www.dropbox.com/s/b28x6a01w7dxsed/plan_fast.xml

Uwaga: tabela, schemat, nazwy obiektów zostały zmienione ze względów bezpieczeństwa.

RThomas
źródło

Odpowiedzi:

43

Zapytanie to

SELECT SUM(Amount) AS SummaryTotal
FROM   PDetail WITH(NOLOCK)
WHERE  ClientID = @merchid
       AND PostedDate BETWEEN @datebegin AND @dateend 

Tabela zawiera 103 129 000 wierszy.

Szybki plan wyszukuje według ClientId z pozostałym predykatem na dzień, ale musi wykonać 96 odnośników, aby pobrać Amount. Część <ParameterList>planu jest następująca.

        <ParameterList>
          <ColumnReference Column="@dateend" 
                           ParameterRuntimeValue="'2013-02-01 23:59:00.000'" />
          <ColumnReference Column="@datebegin" 
                           ParameterRuntimeValue="'2013-01-01 00:00:00.000'" />
          <ColumnReference Column="@merchid" 
                           ParameterRuntimeValue="(78155)" />
        </ParameterList>

Powolny plan wyszukuje według daty i zawiera wyszukiwania w celu oceny rezydualnego predykatu na ClientId i odzyskania kwoty (Szacowany 1 vs Rzeczywisty 7 388 383). Ta <ParameterList>sekcja jest

        <ParameterList>
          <ColumnReference Column="@EndDate" 
                           ParameterCompiledValue="'2013-02-01 23:59:00.000'" 
                           ParameterRuntimeValue="'2013-02-01 23:59:00.000'" />
          <ColumnReference Column="@BeginDate" 
                           ParameterCompiledValue="'2013-01-01 00:00:00.000'"               
                           ParameterRuntimeValue="'2013-01-01 00:00:00.000'" />
          <ColumnReference Column="@ClientID" 
                           ParameterCompiledValue="(78155)" 
                           ParameterRuntimeValue="(78155)" />
        </ParameterList>

W tym drugim przypadku nieParameterCompiledValue jest pusty. SQL Server z powodzeniem wąchał wartości użyte w zapytaniu.

Książka „SQL Server 2005 Praktyczne rozwiązywanie problemów” ma to do powiedzenia na temat używania zmiennych lokalnych

Używanie zmiennych lokalnych w celu pokonania wąchania parametrów jest dość powszechną sztuczką, ale wskazówki OPTION (RECOMPILE)i OPTION (OPTIMIZE FOR)... są na ogół bardziej eleganckie i nieco mniej ryzykowne rozwiązania


Uwaga

W SQL Server 2005 kompilacja na poziomie instrukcji pozwala na odroczenie kompilacji pojedynczej instrukcji w procedurze przechowywanej do momentu tuż przed pierwszym wykonaniem zapytania. Do tego czasu wartość zmiennej lokalnej będzie znana. Teoretycznie SQL Server mógłby to wykorzystać do wąchania wartości zmiennych lokalnych w taki sam sposób, jak wącha parametry. Jednak ponieważ powszechne było używanie zmiennych lokalnych do pokonania wąchania parametrów w SQL Server 7.0 i SQL Server 2000+, wąchanie zmiennych lokalnych nie było włączone w SQL Server 2005. Może być włączone w przyszłej wersji SQL Server, choć jest to dobre powód, aby skorzystać z jednej z innych opcji opisanych w tym rozdziale, jeśli masz wybór.


Z szybkiego testu tego końca zachowanie opisane powyżej pozostaje takie samo w 2008 i 2012 roku, a zmienne nie są wąchane dla odroczonej kompilacji, ale tylko wtedy, gdy OPTION RECOMPILEużyta jest wyraźna wskazówka.

DECLARE @N INT = 0

CREATE TABLE #T ( I INT );

/*Reference to #T means this statement is subject to deferred compile*/
SELECT *
FROM   master..spt_values
WHERE  number = @N
       AND EXISTS(SELECT COUNT(*) FROM #T)

SELECT *
FROM   master..spt_values
WHERE  number = @N
OPTION (RECOMPILE)

DROP TABLE #T 

Pomimo odroczonej kompilacji zmienna nie jest wąchana, a szacowana liczba wierszy jest niedokładna

Szacunki a rzeczywiste

Zakładam więc, że wolny plan dotyczy sparametryzowanej wersji zapytania.

ParameterCompiledValueJest równe ParameterRuntimeValuedla wszystkich parametrów, więc nie jest to typowy parametr wąchania (gdzie plan został opracowany dla jednego zestawu wartości, a następnie uruchomić na inny zestaw wartości).

Problem polega na tym, że plan skompilowany dla poprawnych wartości parametrów jest nieodpowiedni.

Prawdopodobnie trafiasz na problem z rosnącymi datami opisanymi tutaj i tutaj . W przypadku tabeli zawierającej 100 milionów wierszy należy wstawić (lub w inny sposób zmodyfikować) 20 milionów, zanim SQL Server automatycznie zaktualizuje statystyki. Wygląda na to, że ostatnim razem, gdy były aktualizowane, zero wierszy pasowało do zakresu dat w zapytaniu, ale teraz robi to 7 milionów.

Możesz zaplanować częstsze aktualizacje statystyk, rozważyć flagi śledzenia 2389 - 90lub użyć, OPTIMIZE FOR UKNOWNaby po prostu opierać się na domysłach, a nie być w stanie używać obecnie wprowadzających w błąd statystyk w datetimekolumnie.

Może to nie być konieczne w następnej wersji programu SQL Server (po 2012 r.). Związane poz Połącz zawiera intrygującą odpowiedź

Wysłany przez Microsoft 28.08.2012 o 13:35
Zrobiliśmy ulepszenie szacowania liczności dla następnej ważnej wersji, która zasadniczo to naprawia. Sprawdzaj szczegóły, gdy tylko pojawią się nasze zapowiedzi. Eric

Poprawkę z 2014 r. Analizuje Benjamin Nevarez pod koniec artykułu:

Pierwsze spojrzenie na nowy program SQL Server Cardinality Estimator .

Wygląda na to, że nowy estymator liczności cofnie się i zastosuje średnią gęstość w tym przypadku zamiast podawać szacunek 1-rzędowy.

Kilka dodatkowych szczegółów na temat estymatora liczebności 2014 i rosnącego kluczowego problemu tutaj:

Nowa funkcjonalność w SQL Server 2014 - Część 2 - Nowa ocena liczności

Martin Smith
źródło
29

Moje pytanie brzmi więc ... jak można obwiniać wąchanie parametrów, gdy otrzymujemy to samo wolne zapytanie na pustej pamięci podręcznej planu ... nie powinno być żadnych parametrów do wąchania?

Gdy SQL Server kompiluje zapytanie zawierające wartości parametrów, wącha określone wartości tych parametrów w celu oszacowania liczności (liczby wierszy). W twoim przypadku, poszczególne wartości @BeginDate, @EndDatei @ClientIDsą stosowane przy wyborze planu wykonania. Więcej informacji na temat wykrywania parametrów można znaleźć tutaj i tutaj . Podaję te linki w tle, ponieważ powyższe pytanie sprawia, że ​​myślę, że koncepcja jest obecnie niedokładnie zrozumiana - zawsze są wartości parametrów, które należy powąchać podczas kompilacji planu.

W każdym razie, to wszystko nie ma sensu, ponieważ wąchanie parametrów nie jest tutaj problemem, jak zauważył Martin Smith. W chwili kompilacji powolnego zapytania statystyki wskazywały, że nie ma żadnych wierszy dla wąchanych wartości @BeginDatei @EndDate:

Powoli powąchane wartości

Wąchane wartości są bardzo nowe, co sugeruje rosnący kluczowy problem, o którym wspomina Martin. Ponieważ szacuje się, że wyszukiwanie indeksu według dat zwróci tylko jeden wiersz, optymalizator wybiera plan, który przekazuje predykat ClientIDoperatorowi wyszukiwania klucza jako resztę.

Szacowanie w jednym wierszu jest również przyczyną, dla której optymalizator przestaje szukać lepszych planów, zwracając komunikat „Znaleziono wystarczająco dużo planu”. Szacunkowy całkowity koszt powolnego planu z szacunkiem dla jednego wiersza wynosi zaledwie 0,013136 jednostek kosztu, więc nie ma sensu szukać czegoś lepszego. Z wyjątkiem tego, że wyszukiwanie faktycznie zwraca 7,388,383 wierszy zamiast jednego, powodując taką samą liczbę odnośników kluczowych.

Statystyki mogą być trudne do aktualizowania i przydatne w przypadku dużych tabel, a partycjonowanie wprowadza własne wyzwania w tym zakresie. Sam nie odniosłem szczególnego sukcesu z flagami śledzenia 2389 i 2390, ale zapraszamy do ich przetestowania. W nowszych wersjach programu SQL Server (R2 SP1 i nowsze) dostępne są aktualizacje statystyk dynamicznych , ale te aktualizacje statystyk dla poszczególnych partycji wciąż nie są zaimplementowane. W międzyczasie możesz zaplanować ręczną aktualizację statystyk za każdym razem, gdy wprowadzisz znaczące zmiany w tej tabeli.

W przypadku tego konkretnego zapytania pomyślałem o wdrożeniu indeksu sugerowanego przez optymalizatora podczas kompilacji planu szybkiego zapytania:

/*
The Query Processor estimates that implementing the following index could improve
the query cost by 98.8091%.

WARNING: This is only an estimate, and the Query Processor is making this 
recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide impact,
including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/
CREATE NONCLUSTERED INDEX [<Name of Missing Index>]
ON [dbo].[PDetail] ([ClientID],[PostedDate])
INCLUDE ([Amount]);

Indeks powinien być wyrównany do partycji, z ON PartitionSchemeName (PostedDate)klauzulą, ale chodzi o to, że zapewnienie oczywiście najlepszej ścieżki dostępu do danych pomoże optymalizatorowi uniknąć złych wyborów planu, bez uciekania się do OPTIMIZE FOR UNKNOWNwskazówek lub staromodnych obejść, takich jak używanie zmiennych lokalnych.

Dzięki ulepszonemu indeksowi wyszukiwanie klucza w celu pobrania Amountkolumny zostanie wyeliminowane, procesor zapytań może nadal wykonywać dynamiczną eliminację partycji i wyszukiwać w celu znalezienia określonego ClientIDi zakresu dat.

Paul White mówi GoFundMonica
źródło
Chciałbym oznaczyć dwie odpowiedzi jako poprawne, ale jeszcze raz dziękuję za dodatkowe informacje - bardzo pouczające.
RThomas
1
Minęło kilka lat, odkąd to opublikowałem ... ale chciałem ci tylko powiedzieć. Nadal używam terminu „niedokładnie zrozumiany” przez cały ten cholerny czas i zawsze myślę o Paulu Whiteie, kiedy to robię. Za każdym razem sprawia mi śmiech.
RThomas
0

Miałem dokładnie ten sam problem, gdy procedura przechowywana stała się wolna, a OPTIMIZE FOR UNKNOWNi RECOMPILEpodpowiedzi zapytania rozwiązany powolność i przyspieszyć czas realizacji. Jednak następujące dwie metody nie wpłynęły na powolność procedury przechowywanej: (i) Czyszczenie pamięci podręcznej (ii) przy użyciu Z RECOMPILE. Tak jak powiedziałeś, tak naprawdę nie było to wąchanie parametrów.

Flagi śledzenia 2389 i 2390 również nie pomogły. Właśnie aktualizacja statystyki ( EXEC sp_updatestats) zrobiła to dla mnie.

aali
źródło