In SQL Server 2016 CTP 2.1 is er één nieuw object dat is verschenen na CTP 2.0:sys.dm_exec_function_stats. Dit is bedoeld om vergelijkbare functionaliteit te bieden als sys.dm_exec_procedure_stats, sys.dm_exec_query_stats en sys.dm_exec_trigger_stats. Het is nu dus mogelijk om geaggregeerde runtime-statistieken bij te houden voor door de gebruiker gedefinieerde functies.
Of toch?
In CTP 2.1 kon ik hier alleen zinvolle statistieken afleiden voor reguliere scalaire functies - er was niets geregistreerd voor inline of multi-statement TVF's. Ik ben niet verbaasd over de inline-functies, omdat ze sowieso in wezen worden uitgebreid voordat ze worden uitgevoerd. Maar aangezien multi-statement TVF's vaak prestatieproblemen zijn, hoopte ik dat ze ook zouden opduiken. Ze verschijnen nog steeds in sys.dm_exec_query_stats, dus je kunt nog steeds hun prestatiestatistieken daaruit afleiden, maar het kan lastig worden om aggregaties uit te voeren als je echt meerdere verklaringen hebt die een deel van het werk doen - er wordt niets voor je opgerold.
Laten we eens kijken hoe dit uitpakt. Laten we zeggen dat we een eenvoudige tabel hebben met 100.000 rijen:
SELECT TOP (100000) o1.[object_id], o1.create_date INTO dbo.src FROM sys.all_objects AS o1 CROSS JOIN sys.all_objects AS o2 ORDER BY o1.[object_id]; GO CREATE CLUSTERED INDEX x ON dbo.src([object_id]); GO -- prime the cache SELECT [object_id], create_date FROM dbo.src;
Ik wilde vergelijken wat er gebeurt als we scalaire UDF's, multi-statement tabelwaardefuncties en inline tabelwaardefuncties onderzoeken, en hoe we zien welk werk in elk geval is gedaan. Stel je eerst iets triviaals voor dat we kunnen doen in de SELECT
clausule, maar die we misschien weg willen compartimenteren, zoals het formatteren van een datum als een string:
CREATE PROCEDURE dbo.p_dt_Standard @dt_ CHAR(10) = NULL AS BEGIN SET NOCOUNT ON; SELECT @dt_ = CONVERT(CHAR(10), create_date, 120) FROM dbo.src ORDER BY [object_id]; END GO
(Ik wijs de uitvoer toe aan een variabele, die dwingt de hele tabel te scannen, maar voorkomt dat de prestatiestatistieken worden beïnvloed door de inspanningen van SSMS om de uitvoer te consumeren en weer te geven. Bedankt voor de herinnering, Mikael Eriksson.)
Vaak zie je mensen die conversie in een functie zetten, en het kan scalair of TVF zijn, zoals deze:
CREATE FUNCTION dbo.dt_Inline(@dt_ DATETIME) RETURNS TABLE AS RETURN (SELECT dt_ = CONVERT(CHAR(10), @dt_, 120)); GO CREATE FUNCTION dbo.dt_Multi(@dt_ DATETIME) RETURNS @t TABLE(dt_ CHAR(10)) AS BEGIN INSERT @t(dt_) SELECT CONVERT(CHAR(10), @dt_, 120); RETURN; END GO CREATE FUNCTION dbo.dt_Scalar(@dt_ DATETIME) RETURNS CHAR(10) AS BEGIN RETURN (SELECT CONVERT(CHAR(10), @dt_, 120)); END GO
Ik heb als volgt procedure-wrappers rond deze functies gemaakt:
CREATE PROCEDURE dbo.p_dt_Inline @dt_ CHAR(10) = NULL AS BEGIN 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]; END GO CREATE PROCEDURE dbo.p_dt_Multi @dt_ CHAR(10) = NULL AS BEGIN SET NOCOUNT ON; SELECT @dt_ = dt.dt_ FROM dbo.src CROSS APPLY dbo.dt_Multi(create_date) AS dt ORDER BY [object_id]; END GO CREATE PROCEDURE dbo.p_dt_Scalar @dt_ CHAR(10) = NULL AS BEGIN SET NOCOUNT ON; SELECT @dt_ = dt = dbo.dt_Scalar(create_date) FROM dbo.src ORDER BY [object_id]; END GO
(En nee, de dt_
conventie die je ziet, is niet iets nieuws dat volgens mij een goed idee is, het was gewoon de eenvoudigste manier om al deze vragen in de DMV's te isoleren van al het andere dat wordt verzameld. Het maakte het ook gemakkelijk om achtervoegsels toe te voegen om gemakkelijk onderscheid te kunnen maken tussen de zoekopdracht in de opgeslagen procedure en de ad-hocversie.)
Vervolgens heb ik een #temp-tabel gemaakt om timings op te slaan en dit proces herhaald (zowel de opgeslagen procedure twee keer uitvoeren, en de hoofdtekst van de procedure twee keer uitvoeren als een geïsoleerde ad-hocquery, en de timing van elke volgen):
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('ad hoc Standard',SYSDATETIME()); GO DECLARE @dt_st CHAR(10); SELECT @dt_st = CONVERT(CHAR(10), create_date, 120) FROM dbo.src ORDER BY [object_id]; GO 2 UPDATE #t SET e = SYSDATETIME() WHERE ID = 2; GO -- repeat for inline, multi and scalar versions
Daarna heb ik een aantal diagnostische vragen uitgevoerd en dit waren de resultaten:
sys.dm_exec_function_stats
SELECT name = OBJECT_NAME(object_id), execution_count, time_milliseconds = total_elapsed_time/1000 FROM sys.dm_exec_function_stats WHERE database_id = DB_ID() ORDER BY name;
Resultaten:
name execution_count time_milliseconds --------- --------------- ----------------- dt_Scalar 400000 1116
Dat is geen typfout; alleen de scalaire UDF toont enige aanwezigheid in de nieuwe DMV.
sys.dm_exec_procedure_stats
SELECT name = OBJECT_NAME(object_id), execution_count, time_milliseconds = total_elapsed_time/1000 FROM sys.dm_exec_procedure_stats WHERE database_id = DB_ID() ORDER BY name;
Resultaten:
name execution_count time_milliseconds ------------- --------------- ----------------- p_dt_Inline 2 74 p_dt_Multi 2 269 p_dt_Scalar 2 1063 p_dt_Standard 2 75
Dit is geen verrassend resultaat:het gebruik van een scalaire functie leidt tot een prestatiestraf in de orde van grootte, terwijl de multi-statement TVF slechts ongeveer 4x slechter was. Tijdens meerdere tests was de inline-functie altijd zo snel of een milliseconde of twee sneller dan helemaal geen functie.
sys.dm_exec_query_stats
SELECT query = SUBSTRING([text],s,e), execution_count, time_milliseconds FROM ( SELECT t.[text], s = s.statement_start_offset/2 + 1, e = COALESCE(NULLIF(s.statement_end_offset,-1),8000)/2, s.execution_count, time_milliseconds = s.total_elapsed_time/1000 FROM sys.dm_exec_query_stats AS s OUTER APPLY sys.dm_exec_sql_text(s.[sql_handle]) AS t WHERE t.[text] LIKE N'%dt[_]%' ) AS x;
Afgekapte resultaten, handmatig opnieuw geordend:
query (truncated) execution_count time_milliseconds -------------------------------------------------------------------- --------------- ----------------- -- p Standard: SELECT @dt_ = CONVERT(CHAR(10), create_date, 120) ... 2 75 -- ad hoc Standard: SELECT @dt_st = CONVERT(CHAR(10), create_date, 120) ... 2 72 -- p Inline: SELECT @dt_ = dt.dt_ FROM dbo.src AS o CROSS APPLY dbo.dt_Inline... 2 74 -- ad hoc Inline: SELECT @dt_in = dt.dt_ FROM dbo.src AS o CROSS APPLY dbo.dt_Inline... 2 72 -- all Multi: INSERT @t(dt_) SELECT CONVERT(CHAR(10), @dt_, 120); 184 5 -- p Multi: SELECT @dt_ = dt.dt_ FROM dbo.src CROSS APPLY dbo.dt_Multi... 2 270 -- ad hoc Multi: SELECT @dt_m = dt.dt_ FROM dbo.src AS o CROSS APPLY dbo.dt_Multi... 2 257 -- all scalar: RETURN (SELECT CONVERT(CHAR(10), @dt_, 120)); 400000 581 -- p Scalar: SELECT @dt_ = dbo.dt_Scalar(create_date)... 2 986 -- ad hoc Scalar: SELECT @dt_sc = dbo.dt_Scalar(create_date)... 2 902
Een belangrijk ding om hier op te merken is dat de tijd in milliseconden voor de INSERT in de multi-statement TVF en de RETURN-instructie in de scalaire functie ook worden verantwoord binnen de individuele SELECT's, dus het heeft geen zin om alle de tijden.
Handmatige timings
En dan tot slot de timing uit de #temp-tabel:
SELECT query = q, time_milliseconds = DATEDIFF(millisecond, s, e) FROM #t ORDER BY ID;
Resultaten:
query time_milliseconds --------------- ----------------- p Standard 107 ad hoc Standard 78 p Inline 80 ad hoc Inline 78 p Multi 351 ad hoc Multi 263 p Scalar 992 ad hoc Scalar 907
Aanvullende interessante resultaten hier:de procedure-wrapper had altijd wat overhead, maar hoe belangrijk dat is, kan echt subjectief zijn.
Samenvatting
Mijn punt hier vandaag was alleen om de nieuwe DMV in actie te laten zien en de juiste verwachtingen te stellen - sommige prestatiestatistieken voor functies zullen nog steeds misleidend zijn, en sommige zullen nog steeds helemaal niet beschikbaar zijn (of op zijn minst erg vervelend zijn om voor jezelf samen te stellen ).
Ik denk echter dat deze nieuwe DMV een van de grootste onderdelen van het monitoren van query's omvat die SQL Server eerder miste:dat scalaire functies soms onzichtbare prestatiemoordenaars zijn, omdat de enige betrouwbare manier om hun gebruik te identificeren was om de querytekst te ontleden, die is verre van onfeilbaar. Het maakt niet uit dat je daarmee hun impact op de prestaties niet kunt isoleren, of dat je in de eerste plaats had moeten weten dat je op zoek was naar scalaire UDF's in de querytekst.
Bijlage
Ik heb het script bijgevoegd:DMExecFunctionStats.zip
Vanaf CTP1 is hier ook de reeks kolommen:
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 |
Kolommen momenteel in sys.dm_exec_function_stats