W SQL Server 2016 CTP 2.1 pojawił się jeden nowy obiekt, który pojawił się po CTP 2.0:sys.dm_exec_function_stats. Ma to na celu zapewnienie podobnej funkcjonalności do sys.dm_exec_procedure_stats, sys.dm_exec_query_stats i sys.dm_exec_trigger_stats. Dzięki temu możliwe jest teraz śledzenie zagregowanych metryk czasu wykonywania dla funkcji zdefiniowanych przez użytkownika.
A może?
Przynajmniej w CTP 2.1 mogłem tutaj wyprowadzić tylko jakiekolwiek znaczące metryki dla zwykłych funkcji skalarnych – nic nie zostało zarejestrowane dla funkcji TVF wbudowanych lub wielowyrazowych. Nie dziwią mnie funkcje wbudowane, ponieważ i tak są one w zasadzie rozszerzane przed wykonaniem. Ale ponieważ wielowypowiedziowe programy TVF często powodują problemy z wydajnością, miałem nadzieję, że również się pojawią. Nadal pojawiają się w sys.dm_exec_query_stats, więc nadal możesz stamtąd czerpać ich metryki wydajności, ale może być trudno przeprowadzać agregacje, gdy naprawdę masz wiele instrukcji, które wykonują pewną część pracy – nic nie jest dla ciebie skumulowane.
Rzućmy okiem na to, jak to się rozgrywa. Załóżmy, że mamy prostą tabelę zawierającą 100 000 wierszy:
WYBIERZ TOP (100000) o1.[object_id], o1.create_date DO dbo.src Z sys.all_objects JAK o1 POŁĄCZENIE KRZYŻOWE sys.all_objects JAK o2 ZAMÓW WEDŁUG o1.[object_id];GOCREATE SKLASTROWANY INDEKS x NA dbo.src ([object_id]);GO-- zapełnij cacheSELECT [object_id], create_date FROM dbo.src;
Chciałem porównać, co się dzieje, gdy badamy skalarne funkcje UDF, wieloinstrukcyjne funkcje z wartościami przechowywanymi w tabeli i wbudowane funkcje z wartościami w tabeli oraz jak widzimy, jaka praca została wykonana w każdym przypadku. Najpierw wyobraź sobie coś trywialnego, co możemy zrobić w SELECT
klauzulę, ale możemy chcieć oddzielić ją od siebie, jak formatowanie daty jako ciągu:
CREATE PROCEDURE dbo.p_dt_Standard @dt_ CHAR(10) =NULLASBEGIN SET NOCOUNT ON; SELECT @dt_ =CONVERT(CHAR(10), data_utworzenia, 120) FROM dbo.src ORDER BY [identyfikator_obiektu];ENDGO
(Przypisuję dane wyjściowe do zmiennej, która wymusza przeskanowanie całej tabeli, ale zapobiega wpływowi wysiłków SSMS na wykorzystanie i renderowanie danych wyjściowych przez metryki wydajności. Dziękuję za przypomnienie, Mikael Eriksson.)
Wiele razy zobaczysz, jak ludzie umieszczają tę konwersję w funkcji, która może być skalarna lub TVF, na przykład:
CREATE FUNCTION dbo.dt_Inline(@dt_ DATETIME)RETURNS TABLEAS RETURN (SELECT dt_ =CONVERT(CHAR(10), @dt_, 120));GO CREATE FUNCTION dbo.dt_Multi(@dt_ DATETIME)RETURNS @t TABLE( dt_ ZNAK(10))ASBEGIN INSERT @t(dt_) SELECT CONVERT(ZNAK(10), @dt_, 120); RETURN;ENDGO CREATE FUNCTION dbo.dt_Scalar(@dt_ DATETIME)RETURNS CHAR(10)ASBEGIN RETURN (SELECT CONVERT(CHAR(10), @dt_, 120));ENDGO
Stworzyłem otoki procedur wokół tych funkcji w następujący sposób:
CREATE PROCEDURE dbo.p_dt_Inline @dt_ CHAR(10) =NULLASBEGIN SET NOCOUNT ON; SELECT @dt_ =dt.dt_ FROM dbo.src AS o CROSS APPLY dbo.dt_Inline(o.create_date) AS dt ORDER BY o.[object_id];ENDGO CREATE PROCEDURE dbo.p_dt_Multi @dt_ CHAR(10) =NULLAS SET NOCOUNT; SELECT @dt_ =dt.dt_ FROM dbo.src CROSS APPLY dbo.dt_Multi(create_date) AS dt ORDER BY [object_id];ENDGO CREATE PROCEDURE dbo.p_dt_Scalar @dt_ CHAR(10) =NULLASBEGIN SET NOCOUNT ON; SELECT @dt_ =dt =dbo.dt_Scalar(create_date) FROM dbo.src ORDER BY [object_id];ENDGO
(I nie, dt_
Konwencja, którą widzisz, nie jest niczym nowym, myślę, że jest dobrym pomysłem, był to po prostu najprostszy sposób, w jaki mogłem odizolować wszystkie te zapytania w DMV od wszystkiego, co zostało zebrane. Ułatwiło to również dodawanie przyrostków, aby łatwo odróżnić zapytanie wewnątrz procedury składowanej od wersji ad hoc).
Następnie utworzyłem tabelę #temp do przechowywania czasów i powtórzyłem ten proces (zarówno dwukrotne wykonanie procedury składowanej, jak i dwukrotne wykonanie treści procedury jako izolowanego zapytania ad hoc oraz śledzenie czasu każdego z nich):
CREATE TABLE #t( ID INT IDENTITY(1,1), q VARCHAR(32), s DATETIME2, e DATETIME2);GO INSERT #t(q,s) VALUES('p Standard',SYSDATETIME());GO EXEC dbo.p_dt_Standard;GO 2 UPDATE #t SET e =SYSDATETIME() WHERE ID =1;GO INSERT #t(q,s) VALUES('standard ad hoc',SYSDATETIME());GO DECLARE @dt_st CHAR (10); SELECT @dt_st =CONVERT(CHAR(10), create_date, 120) FROM dbo.src ORDER BY [identyfikator_obiektu];GO 2 UPDATE #t SET e =SYSDATETIME() WHERE ID =2;GO-- powtórz dla inline, multi i wersje skalarne
Następnie wykonałem kilka zapytań diagnostycznych i oto wyniki:
sys.dm_exec_function_stats
SELECT nazwa =NAZWA_OBIEKTU(id_obiektu), liczba_wykonań, czas_milisekund =całkowity_upływ czasu/1000FROM sys.dm_exec_function_statsWHERE database_id =DB_ID()ORDER BY name;
Wyniki:
nazwa liczba_wykonań czas_milisekundy--------------------------- -----------------dt_Scalar 400000 1116
To nie jest literówka; tylko skalarny UDF pokazuje jakąkolwiek obecność w nowym DMV.
sys.dm_exec_procedure_stats
SELECT nazwa =NAZWA_OBIEKTU (identyfikator_obiektu), liczba_wykonań, czas_milisekund =całkowity_upływ czasu/1000FROM sys.dm_exec_procedure_statsWHERE database_id =DB_ID()ORDER BY name;
Wyniki:
nazwa liczba_wykonań czas_milisekundy--------------- --------------- ----- -p_dt_Inline 2 74p_dt_Multi 2 269p_dt_Scalar 2 1063p_dt_Standard 2 75
Nie jest to zaskakujący wynik:użycie funkcji skalarnej prowadzi do spadku wydajności o rząd wielkości, podczas gdy wielowyrazowy TVF był tylko około 4 razy gorszy. W wielu testach funkcja wbudowana była zawsze tak szybka lub o milisekundę lub dwie szybsze niż żadna funkcja.
sys.dm_exec_query_stats
Skrócone wyniki, ponownie uporządkowane ręcznie:
Ważną rzeczą, na którą należy zwrócić uwagę, jest to, że czas w milisekundach dla instrukcji INSERT w wielowyrazowej instrukcji TVF i instrukcji RETURN w funkcji skalarnej są również uwzględniane w poszczególnych SELECTach, więc nie ma sensu po prostu zsumować wszystkich czasy.
Czasy ręczne
I wreszcie czasy z tabeli #temp:
Zapytanie SELECT =q, czas_milisekundy =DATEDIFF(milisekunda, s, e) FROM #t ORDER BY ID;
Wyniki:
czas zapytania_milisekundy--------------- -----------------p Standardowy 107ad hoc Standardowy 78p Wbudowany 80ad hoc Wbudowany 78p Multi 351ad hoc Multi 263p Skalar 992ad hoc Skalar 907
Dodatkowe interesujące wyniki:otoczka procedury zawsze wiązała się z pewnym obciążeniem, chociaż jego znaczenie może być naprawdę subiektywne.
Podsumowanie
Chodziło mi o to, aby dzisiaj pokazać nowy DMV w akcji i poprawnie ustawić oczekiwania – niektóre wskaźniki wydajności funkcji nadal będą mylące, a niektóre nadal będą w ogóle niedostępne (lub przynajmniej będą bardzo żmudne, aby samemu złożyć je w całość ).
Myślę, że ten nowy DMV obejmuje jeden z największych elementów monitorowania zapytań, których wcześniej brakowało SQL Server:funkcje skalarne są czasami niewidocznymi zabójcami wydajności, ponieważ jedynym niezawodnym sposobem identyfikacji ich użycia było przeanalizowanie tekstu zapytania, co jest daleka od niezawodnego. Nieważne, że nie pozwoli to na wyizolowanie ich wpływu na wydajność, lub że musiałbyś wiedzieć, że szukasz skalarnych UDF w tekście zapytania.
Załącznik
Załączam skrypt:DMExecFunctionStats.zip
Ponadto, od CTP1, oto zestaw kolumn:
database_id | object_id | type | type_desc | |
sql_handle | plan_handle | cached_time | last_execution_time | execution_count |
total_worker_time | last_worker_time | min_worker_time | max_worker_time | |
total_physical_reads | last_physical_reads | min_physical_reads | max_physical_reads | |
total_logical_writes | last_logical_writes | min_logical_writes | max_logical_writes | |
total_logical_reads | last_logical_reads | min_logical_reads | max_logical_reads | |
total_elapsed_time | last_elapsed_time | min_elapsed_time | max_elapsed_time |
Kolumny aktualnie w sys.dm_exec_function_stats