sql >> Database >  >> RDS >> Sqlserver

SQL Server 2016:sys.dm_exec_function_stats

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


  1. Waarom retourneert PostgreSQL geen null-waarden als de voorwaarde <> waar is?

  2. Wat is het equivalent van varchar(max) in Oracle?

  3. Android:hoe de afbeelding dynamisch van de server te laden op naam van SQlite

  4. Hoe u records van de laatste 15 dagen in MySQL kunt krijgen