I SQL Server 2016 CTP 2.1 er der et nyt objekt, der dukkede op efter CTP 2.0:sys.dm_exec_function_stats. Dette er beregnet til at give lignende funktionalitet til sys.dm_exec_procedure_stats, sys.dm_exec_query_stats og sys.dm_exec_trigger_stats. Så det er nu muligt at spore samlede runtime-metrics for brugerdefinerede funktioner.
Eller er det?
I det mindste i CTP 2.1 kunne jeg kun udlede nogen meningsfuld metrik her for almindelige skalarfunktioner - intet blev registreret for inline eller multi-statement TVF'er. Jeg er ikke overrasket over de inline-funktioner, da de i det væsentlige er udvidet inden udførelse alligevel. Men da multi-statement TVF'er ofte er præstationsproblemer, håbede jeg, at de også ville dukke op. De vises stadig i sys.dm_exec_query_stats, så du kan stadig udlede deres præstationsmålinger derfra, men det kan blive svært at udføre aggregeringer, når du virkelig har flere udsagn, der udfører en del af arbejdet – intet er rullet op for dig.
Lad os tage et hurtigt kig på, hvordan dette udspiller sig. Lad os sige, at vi har en simpel tabel med 100.000 rækker:
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;
Jeg ønskede at sammenligne, hvad der sker, når vi undersøger skalære UDF'er, multi-statement tabel-værdisatte funktioner og inline tabel-værdisatte funktioner, og hvordan vi ser, hvilket arbejde der blev udført i hvert enkelt tilfælde. Forestil dig først noget trivielt, som vi kan gøre i SELECT
klausul, men som vi måske ønsker at opdele væk, som at formatere en dato som en streng:
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
(Jeg tildeler outputtet til en variabel, som tvinger hele tabellen til at blive scannet, men forhindrer præstationsmålingerne i at blive påvirket af SSMS's bestræbelser på at forbruge og gengive outputtet. Tak for påmindelsen, Mikael Eriksson.)
Mange gange vil du se folk sætte den konvertering ind i en funktion, og det kan være skalært eller TVF, som disse:
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
Jeg oprettede procedureindpakninger omkring disse funktioner som følger:
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
(Og nej, dt_
konvention, du ser, er ikke noget nyt, jeg synes er en god idé, det var bare den enkleste måde, jeg kunne isolere alle disse forespørgsler i DMV'erne fra alt andet, der blev indsamlet. Det gjorde det også nemt at tilføje suffikser for nemt at skelne mellem forespørgslen i den lagrede procedure og ad hoc-versionen.)
Dernæst oprettede jeg en #temp-tabel til at gemme timings, og gentog denne proces (både ved at udføre den lagrede procedure to gange og udføre brødteksten af proceduren som en isoleret ad hoc-forespørgsel to gange og spore timingen af hver enkelt):
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
Så kørte jeg nogle diagnostiske forespørgsler, og her var resultaterne:
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;
Resultater:
name execution_count time_milliseconds --------- --------------- ----------------- dt_Scalar 400000 1116
Det er ikke en tastefejl; kun den skalære UDF viser nogen tilstedeværelse i den nye 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;
Resultater:
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
Dette er ikke et overraskende resultat:Brug af en skalarfunktion fører til en præstationsstraf i størrelsesorden, mens TVF med flere erklæringer kun var omkring 4x værre. Over flere tests var inline-funktionen altid lige så hurtig eller et millisekund eller to hurtigere end ingen funktion overhovedet.
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;
Trunkerede resultater, genbestillet manuelt:
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
En vigtig ting at bemærke her er, at tiden i millisekunder for INSERT i multi-sætningen TVF og RETURN-sætningen i skalarfunktionen også tages med i de individuelle SELECT'er, så det giver ikke mening blot at lægge alle sammen tidspunkterne.
Manuelle tidsindstillinger
Og så til sidst, timingen fra #temp-tabellen:
SELECT query = q, time_milliseconds = DATEDIFF(millisecond, s, e) FROM #t ORDER BY ID;
Resultater:
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
Yderligere interessante resultater her:Procedureindpakningen havde altid nogle overhead, men hvor betydningsfuldt det er, kan være virkelig subjektivt.
Oversigt
Min pointe her i dag var blot at vise den nye DMV i aktion og sætte forventningerne korrekt – nogle præstationsmålinger for funktioner vil stadig være vildledende, og nogle vil stadig ikke være tilgængelige overhovedet (eller i det mindste være meget kedelige at sammensætte for dig selv ).
Jeg tror dog, at denne nye DMV dækker en af de største dele af forespørgselsovervågning, som SQL Server manglede før:at skalarfunktioner nogle gange er usynlige præstationsdræbere, fordi den eneste pålidelige måde at identificere deres brug på var at parse forespørgselsteksten, som er langt fra idiotsikker. Husk det faktum, at det ikke giver dig mulighed for at isolere deres indvirkning på ydeevnen, eller at du i første omgang skulle have vidst, at du leder efter skalære UDF'er i forespørgselsteksten.
Bilag
Jeg har vedhæftet scriptet:DMExecFunctionStats.zip
Fra CTP1 er her også rækken af kolonner:
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 |
Kolonner i øjeblikket i sys.dm_exec_function_stats