sql >> Databasteknik >  >> RDS >> Sqlserver

SQL Server 2016:sys.dm_exec_function_stats

I SQL Server 2016 CTP 2.1 finns det ett nytt objekt som dök upp efter CTP 2.0:sys.dm_exec_function_stats. Detta är avsett att tillhandahålla liknande funktionalitet som sys.dm_exec_procedure_stats, sys.dm_exec_query_stats och sys.dm_exec_trigger_stats. Så det är nu möjligt att spåra samlade körtidsmått för användardefinierade funktioner.

Eller är det?

Åtminstone i CTP 2.1 kunde jag bara härleda någon meningsfull statistik här för vanliga skalära funktioner – ingenting registrerades för inline- eller multi-statement TVFs. Jag är inte förvånad över inline-funktionerna, eftersom de i princip utökas innan de körs i alla fall. Men eftersom TVF med flera påståenden ofta är prestandaproblem, hoppades jag att de också skulle dyka upp. De visas fortfarande i sys.dm_exec_query_stats, så du kan fortfarande härleda deras prestandamått därifrån, men det kan bli svårt att utföra aggregering när du verkligen har flera påståenden som utför en del av arbetet – ingenting är upprullat för dig.

Låt oss ta en snabb titt på hur det här ser ut. Låt oss säga att vi har en enkel tabell med 100 000 rader:

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;

Jag ville jämföra vad som händer när vi undersöker skalära UDF:er, multi-statement tabell-värderade funktioner och inline tabell-värderade funktioner, och hur vi ser vilket arbete som gjordes i varje enskilt fall. Först, föreställ dig något trivialt som vi kan göra i SELECT sats, men som vi kanske vill dela bort, som att formatera ett datum som en sträng:

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

(Jag tilldelar utdata till en variabel, som tvingar hela tabellen att skannas, men förhindrar att prestandamåtten påverkas av SSMS ansträngningar att konsumera och återge utdata. Tack för påminnelsen, Mikael Eriksson.)

Många gånger ser du människor som lägger in den konverteringen i en funktion, och det kan vara skalärt eller TVF, som dessa:

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

Jag skapade proceduromslag runt dessa funktioner enligt följande:

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

(Och nej, dt_ konventionen du ser är inte någon ny sak som jag tycker är en bra idé, det var bara det enklaste sättet jag kunde isolera alla dessa frågor i DMV från allt annat som samlas in. Det gjorde det också enkelt att lägga till suffix för att enkelt kunna skilja mellan frågan i den lagrade proceduren och ad hoc-versionen.)

Därefter skapade jag en #temp-tabell för att lagra timings och upprepade den här processen (både exekvera den lagrade proceduren två gånger och exekvera procedurens brödtext som en isolerad ad hoc-fråga två gånger, och spåra timingen för var och en):

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

Sedan körde jag några diagnostiska frågor, och här var 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;

Resultat:

name        execution_count    time_milliseconds
---------   ---------------    -----------------
dt_Scalar   400000             1116

Det är inte ett stavfel; endast den skalära UDF visar någon närvaro i den nya 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;

Resultat:

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

Detta är inte ett överraskande resultat:att använda en skalär funktion leder till en prestationsstraff i storleksordning, medan TVF med flera påståenden bara var cirka 4 gånger värre. Under flera tester var inline-funktionen alltid lika snabb eller en millisekund eller två snabbare än ingen funktion alls.

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;

Trunkerade resultat, ombeställda manuellt:

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 viktig sak att notera här är att tiden i millisekunder för INSERT i multisatsen TVF och RETURN-satsen i den skalära funktionen också redovisas inom de individuella SELECT:erna, så det är inte meningsfullt att bara lägga ihop alla tiderna.

Manuella tidtagningar

Och så till sist, tiderna från #temp-tabellen:

SELECT query = q, 
    time_milliseconds = DATEDIFF(millisecond, s, e) 
  FROM #t 
  ORDER BY ID;

Resultat:

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

Ytterligare intressanta resultat här:proceduromslaget hade alltid en viss omkostnad, men hur betydelsefullt det är kan vara riktigt subjektivt.

Sammanfattning

Min poäng här idag var bara att visa den nya DMV i aktion och ställa in förväntningarna korrekt – vissa prestandamått för funktioner kommer fortfarande att vara missvisande, och vissa kommer fortfarande inte att vara tillgängliga alls (eller åtminstone vara väldigt tråkiga att sätta ihop själv ).

Jag tror dock att den här nya DMV täcker en av de största delarna av frågeövervakning som SQL Server saknade tidigare:att skalära funktioner ibland är osynliga prestandadödare, eftersom det enda tillförlitliga sättet att identifiera deras användning var att analysera frågetexten, vilket är långt ifrån idiotsäker. Strunt i det faktum att det inte tillåter dig att isolera deras inverkan på prestanda, eller att du måste ha känt till att du letar efter skalära UDF:er i frågetexten i första hand.

Bilaga

Jag har bifogat skriptet:DMExecFunctionStats.zip

Från och med CTP1 är här uppsättningen kolumner:

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

Kolumner för närvarande i sys.dm_exec_function_stats


  1. Åtkomst till databasen för en applikation från en annan applikation

  2. Undviker nyckelordsliknande kolumnnamn i Postgres

  3. Addnode resolv.conf Fel

  4. Hur man mappar PostgreSQL-arrayfält i Django ORM