W tym tygodniu uczymy IEPTO2 w Dublinie (a jeśli Irlandii nie ma na Twojej liście miejsc do zobaczenia w tym życiu, musisz to dodać… tu jest fantastycznie) i dzisiaj skończyłem moduł Query Plan Analysis. Jedną z rzeczy, które omawiam, są interesujące rzeczy, które można znaleźć w planie zapytań, na przykład:
- NoJoinPredicate (2005 i nowsze)
- ColumnsWithNoStatistics (2005 i nowsze)
- Niezrównane indeksy (2008 i nowsze)
- PlanAffectingConvert (2012 i nowsze)
Warto zwrócić uwagę na te atrybuty, gdy patrzysz na pojedynczy plan lub zestaw planów podczas dostrajania. Ale jeśli chcesz być trochę bardziej proaktywny, możesz zacząć wydobywać pamięć podręczną planów i tam ich szukać. Oczywiście wykonanie tego wymaga napisania XQuery, ponieważ plany są w formacie XML (szczegółowe informacje na temat schematu showplan znajdują się na stronie:http://schemas.microsoft.com/sqlserver/2004/07/showplan/). Nie lubię XML-a, choć nie z braku prób, i kiedy jeden z uczestników zapytał, czy można przechwytywać zapytania, które mają atrybut NoJoinPredicate poprzez zdarzenia rozszerzone, pomyślałem:„Co za świetny pomysł, będę musiał sprawdzić ”.
Rzeczywiście, jest na to wydarzenie. Jest wydarzenie dla wszystkich czterech wymienionych powyżej:
- missing_join_predicate
- missing_column_statistics
- unmatched_filtered_indexes
- plan_affecting_convert
Ładny. Konfigurowanie ich w sesji Extended Events jest dość proste. W takim przypadku zalecałbym użycie celu event_file, ponieważ prawdopodobnie rozpoczniesz sesję zdarzeń i pozwolisz jej działać przez chwilę, zanim wrócisz i przejrzysz dane wyjściowe. Zamieściłem kilka działań, mając nadzieję, że te zdarzenia nie zostaną uruchomione że często, więc nie dodajemy tutaj zbyt wiele. Dołączyłem sql_text, mimo że nie jest to akcja, na której naprawdę powinieneś polegać. Jonathan omówił to wcześniej, ale sql_text podaje tylko bufor wejściowy, więc możesz nie otrzymać pełnej historii zapytania. Z tego powodu dołączyłem również plan_handle. Zastrzeżenie polega na tym, że w zależności od tego, kiedy szukasz planu, może on już nie znajdować się w pamięci podręcznej planu.
-- Remove event session if it exists IF EXISTS (SELECT 1 FROM [sys].[server_event_sessions] WHERE [name] = 'InterestingPlanEvents') BEGIN DROP EVENT SESSION [InterestingPlanEvents] ON SERVER END GO -- Define event session CREATE EVENT SESSION [InterestingPlanEvents] ON SERVER ADD EVENT sqlserver.missing_column_statistics ( ACTION(sqlserver.database_id,sqlserver.plan_handle,sqlserver.sql_text) WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)) AND [sqlserver].[database_id]>(4)) ), ADD EVENT sqlserver.missing_join_predicate ( ACTION(sqlserver.database_id,sqlserver.plan_handle,sqlserver.sql_text) WHERE ([sqlserver].[is_system]=(0) AND [sqlserver].[database_id]>(4)) ), ADD EVENT sqlserver.plan_affecting_convert ( ACTION(sqlserver.database_id,sqlserver.plan_handle,sqlserver.sql_text) WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)) AND [sqlserver].[database_id]>(4)) ), ADD EVENT sqlserver.unmatched_filtered_indexes ( ACTION(sqlserver.plan_handle,sqlserver.sql_text) WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)) AND [sqlserver].[database_id]>(4)) ) ADD TARGET package0.event_file ( SET filename=N'C:\temp\InterestingPlanEvents' /* change location if appropriate */ ) WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS, MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE, TRACK_CAUSALITY=ON,STARTUP_STATE=OFF) GO -- Start the event session ALTER EVENT SESSION [InterestingPlanEvents] ON SERVER STATE=START; GO
Po uruchomieniu sesji zdarzeń możemy wygenerować te zdarzenia za pomocą przykładowego kodu poniżej. Zauważ, że ten kod zakłada nową instalację AdventureWorks2014. Jeśli go nie masz, możesz nie zobaczyć zdarzenia missing_column_statistics, które zostanie uruchomione, jeśli zostaniesz zapytany o kolumnę [Data zatrudnienia] w [HumanResources].[Pracownik].
-- These queries assume a FRESH restore of AdventureWorks2014 ALTER DATABASE [AdventureWorks2014] SET AUTO_CREATE_STATISTICS OFF; GO USE [AdventureWorks2014]; GO CREATE INDEX [NCI_SalesOrderHeader] ON [Sales].[SalesOrderHeader] ( [PurchaseOrderNumber], [CustomerID], [TotalDue], [DueDate] ) WHERE [SubTotal] > 10000.00; GO /* No join predicate NOTE: We clear procedure here because the event ONLY fires for the *initial* compilation */ DBCC FREEPROCCACHE; /* Not for production use */ SELECT [h].[SalesOrderID], [d].[SalesOrderDetailID], [h].[CustomerID] FROM [Sales].[SalesOrderDetail] [d], [Sales].[SalesOrderHeader] [h] WHERE [d].[ProductID] = 897; GO -- Columns with no statistics SELECT [BusinessEntityID], [NationalIDNumber], [JobTitle], [HireDate], [ModifiedDate] FROM [HumanResources].[Employee] WHERE [HireDate] >= '2013-01-01'; GO -- Unmatched Index DECLARE @Total MONEY = 10000.00; SELECT [PurchaseOrderNumber], [CustomerID], [TotalDue], [DueDate] FROM [Sales].[SalesOrderHeader] WHERE [SubTotal] > @Total; GO -- Plan Affecting Convert SELECT [BusinessEntityID], [NationalIDNumber], [JobTitle], [HireDate], [ModifiedDate] FROM [HumanResources].[Employee] WHERE [NationalIDNumber] = 345106466; GO ALTER EVENT SESSION [InterestingPlanEvents] ON SERVER STATE=STOP; GO DROP EVENT SESSION [InterestingPlanEvents] ON SERVER; GO
UWAGA:PO zakończeniu pobierania planów z pamięci podręcznej możesz uruchomić instrukcję ALTER, aby włączyć opcję automatycznego tworzenia statystyk. Wykonanie tego w tym momencie wyczyści pamięć podręczną planu i będziesz musiał zacząć od początku testowanie. (A także poczekaj, aż skończysz, aby usunąć indeks.)
ALTER DATABASE [AdventureWorks2014] SET AUTO_CREATE_STATISTICS ON; GO DROP INDEX [NCI_SalesOrderHeader] ON [Sales].[SalesOrderHeader]; GO
Ponieważ zatrzymałem sesję zdarzeń, otworzę plik wyjściowy w SSMS, aby zobaczyć, co przechwyciliśmy:
Wyniki zdarzeń rozszerzonych
W przypadku naszego pierwszego zapytania z brakującym predykatem złączenia pojawia się jedno zdarzenie i widzę tekst zapytania w polu sql_text. Jednak to, czego naprawdę chcę, to również spojrzeć na plan, abym mógł wziąć plan_handle i sprawdzić sys.dm_exec_query_plan:
SELECT query_plan FROM sys.dm_exec_query_plan (0x06000700E2200333405DD12C0000000001000000000000000000000000000000000000000000000000000000);
I otwierając to w SQL Sentry Plan Explorer:
Brak predykatu sprzężenia
Plan ma wizualny wskaźnik brakującego predykatu sprzężenia w zagnieżdżonej pętli (czerwony X), a jeśli najadę nad nim, zobaczę ostrzeżenie (i jest ono w XML planu). Świetnie… Mogę teraz porozmawiać z moimi programistami o przepisaniu tego zapytania.
Następne zdarzenie dotyczy statystyki brakującej kolumny. Całkowicie wymusiłem tę sytuację, wyłączając AUTO_CREATE_STATISTICS dla bazy danych AdventureWorks2014. Nie polecam tego w żaden sposób, kształtu ani formy. Ta opcja jest domyślnie włączona i polecam zawsze pozostawiać ją włączoną. Wyłączenie go jest jednak najłatwiejszym sposobem wygenerowania tego zdarzenia. Znowu mam zapytanie w polu sql_text, ale ponownie użyję plan_handle, aby wyciągnąć plan:
SELECT query_plan FROM sys.dm_exec_query_plan (0x060007004448323810921C360000000001000000000000000000000000000000000000000000000000000000);
Brakujące statystyki
I znowu mamy wizualną wskazówkę (żółty trójkąt z wykrzyknikiem), aby wskazać, że jest problem z planem, i znowu jest to w XML. Od tego momentu najpierw sprawdziłbym, czy AUTO_CREATE_STATISTICS jest wyłączone, a jeśli nie, zacząłbym uruchamiać zapytanie w Management Studio, aby sprawdzić, czy mogę odtworzyć ostrzeżenie (i wymusić utworzenie statystyk).
Teraz pozostałe wydarzenia są nieco bardziej interesujące.
Zauważysz, że mamy trzy zdarzenia unmatched_filtered_indexes. Muszę jeszcze ustalić dlaczego, ale pracuję nad tym i opublikuję w komentarzach, czy/kiedy to posortuję. Na razie wystarczy, że mam zdarzenie, a w ramach zdarzenia możemy również zobaczyć informacje o obiekcie, więc znam odpowiedni indeks:
Indeks NCI_SalesOrderHeader, do którego odwołuje się zdarzenie braku indeksu
I znowu mogę wziąć plan_handle, aby znaleźć plan zapytań:
Niedopasowany indeks
Tym razem widzę ostrzeżenie w operatorze SELECT, więc wiem, że jest coś, co muszę dokładniej zbadać. W takim przypadku masz opcje, aby optymalizator używał filtrowanego indeksu, gdy używasz parametrów, i polecam przejrzenie postu Aarona, aby uzyskać więcej informacji na temat korzystania z filtrowanych indeksów.
Na koniec mamy dziewięć wydarzeń dla plan_affecting_convert. Co za cholera? Nadal zastanawiam się nad tym, ale użyłem opcji Śledź przyczynowość w mojej sesji zdarzeń (podczas testowania), aby potwierdzić, że wszystkie zdarzenia są częścią tego samego zadania (są). Jeśli spojrzysz na element wyrażenia w danych wyjściowych, zobaczysz, że nieznacznie się zmienia (podobnie jak compile_time) i jest to widoczne, gdy spojrzysz na szczegóły ostrzeżenia w Eksploratorze planów SQL Sentry (patrz drugi zrzut ekranu poniżej). W wyniku zdarzenia element wyrażenia robi powiedz nam, jaka kolumna jest zaangażowana, co jest początkiem, ale za mało informacji, więc znowu musimy przejść do planu:
SELECT query_plan FROM sys.dm_exec_query_plan (0x0600070023747010E09E1C360000000001000000000000000000000000000000000000000000000000000000);
Plan mający wpływ na konwersję
Szczegóły konwersji z planu
Znowu widzimy naszego przyjaciela, żółty trójkąt, w operatorze SELECT, aw XML możemy znaleźć atrybut PlanAffectingConvert. Ten atrybut został dodany w schemacie programu SQL Server 2012 showplan, więc jeśli korzystasz ze starszej wersji, nie zobaczysz tego w planie. Rozwiązanie tego ostrzeżenia może wymagać nieco więcej pracy – musisz zrozumieć, gdzie występuje niezgodność typu danych i dlaczego, a następnie zacząć modyfikować kod lub schemat… oba mogą spotkać się z oporem. Jonathan ma post, który bardziej szczegółowo omawia niejawną konwersję, co jest dobrym miejscem na rozpoczęcie, jeśli wcześniej nie pracowałeś z problemami z konwersją.
Podsumowanie
Biblioteka zdarzeń Extended Events stale się powiększa, a jedną rzeczą do rozważenia podczas rozwiązywania problemów w SQL Server jest to, czy możesz uzyskać informacje, których szukasz w inny sposób. Być może dlatego, że jest łatwiejszy (z pewnością wolę XE od XML!), lub dlatego, że jest bardziej wydajny lub zapewnia więcej szczegółów. Niezależnie od tego, czy aktywnie szukasz problemów z zapytaniami w swoim środowisku, czy reagujesz na problem zgłoszony przez kogoś, ale masz problem ze znalezieniem go, zdarzenia rozszerzone są opłacalną opcją do rozważenia, szczególnie w przypadku dodania większej liczby nowych funkcji do programu SQL Server.