sql >> Database teknologi >  >> RDS >> Sqlserver

SQL Server 2016:sys.dm_exec_function_stats

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


  1. Sådan fungerer LTRIM_ORACLE() i MariaDB

  2. Oracle SQL:Kolonne ikke tilladt

  3. Oprettelse og implementering af flere versioner af databasen gennem skemasnapshots

  4. Sådan installeres SQL Server på SUSE 12