Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

SQL Server 2016:sys.dm_exec_function_stats

In SQL Server 2016 CTP 2.1 gibt es ein neues Objekt, das nach CTP 2.0 erschienen ist:sys.dm_exec_function_stats. Dies soll ähnliche Funktionen wie sys.dm_exec_procedure_stats, sys.dm_exec_query_stats und sys.dm_exec_trigger_stats bereitstellen. Daher ist es jetzt möglich, aggregierte Laufzeitmetriken für benutzerdefinierte Funktionen zu verfolgen.

Oder doch?

Zumindest in CTP 2.1 konnte ich hier nur für reguläre Skalarfunktionen sinnvolle Metriken ableiten – für Inline- oder Multi-Statement-TVFs wurde nichts registriert. Ich bin nicht überrascht über die Inline-Funktionen, da sie ohnehin vor der Ausführung im Wesentlichen erweitert werden. Aber da Multi-Statement-TVFs oft Leistungsprobleme sind, hatte ich gehofft, dass sie auch auftauchen würden. Sie erscheinen immer noch in sys.dm_exec_query_stats, sodass Sie ihre Leistungsmetriken immer noch von dort ableiten können, aber es kann schwierig werden, Aggregationen durchzuführen, wenn Sie wirklich mehrere Anweisungen haben, die einen Teil der Arbeit erledigen – nichts wird für Sie zusammengefasst.

Werfen wir einen kurzen Blick darauf, wie sich das auswirkt. Nehmen wir an, wir haben eine einfache Tabelle mit 100.000 Zeilen:

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;

Ich wollte vergleichen, was passiert, wenn wir skalare UDFs, Tabellenwertfunktionen mit mehreren Anweisungen und Inline-Tabellenwertfunktionen untersuchen, und wie wir sehen, welche Arbeit in jedem Fall geleistet wurde. Stellen Sie sich zunächst etwas Triviales vor, das wir in SELECT tun können -Klausel, die wir aber vielleicht aufteilen möchten, wie das Formatieren eines Datums als Zeichenfolge:

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

(Ich weise die Ausgabe einer Variablen zu, die das Scannen der gesamten Tabelle erzwingt, aber verhindert, dass die Leistungsmetriken durch die Bemühungen von SSMS, die Ausgabe zu konsumieren und zu rendern, beeinflusst werden. Danke für die Erinnerung, Mikael Eriksson.)

Oft werden Sie Leute sehen, die diese Konvertierung in eine Funktion umwandeln, und es kann sich dabei um Skalare oder TVF handeln, wie diese hier:

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

Ich habe Prozedur-Wrapper um diese Funktionen wie folgt erstellt:

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

(Und nein, die dt_ Die Konvention, die Sie sehen, ist keine neue Sache, die ich für eine gute Idee halte. Es war nur die einfachste Möglichkeit, all diese Abfragen in den DMVs von allem anderen zu isolieren, das gesammelt wird. Es machte es auch einfach, Suffixe anzuhängen, um leicht zwischen der Abfrage innerhalb der gespeicherten Prozedur und der Ad-hoc-Version zu unterscheiden.)

Als Nächstes habe ich eine #temp-Tabelle erstellt, um Timings zu speichern, und diesen Vorgang wiederholt (sowohl die gespeicherte Prozedur zweimal ausgeführt als auch den Hauptteil der Prozedur als isolierte Ad-hoc-Abfrage zweimal ausgeführt und jeweils das Timing verfolgt):

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

Dann habe ich einige diagnostische Abfragen ausgeführt, und hier waren die Ergebnisse:

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;

Ergebnisse:

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

Das ist kein Tippfehler; nur die skalare UDF zeigt irgendeine Präsenz in der neuen 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;

Ergebnisse:

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

Dies ist kein überraschendes Ergebnis:Die Verwendung einer Skalarfunktion führt zu einer Leistungseinbuße in Größenordnungen, während die Multi-Statement-TVF nur etwa 4x schlechter war. Bei mehreren Tests war die Inline-Funktion immer so schnell oder ein oder zwei Millisekunden schneller als überhaupt keine Funktion.

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;

Abgeschnittene Ergebnisse, manuell neu geordnet:

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

Wichtig dabei ist, dass die Zeit in Millisekunden für das INSERT in der Multi-Anweisung TVF und die RETURN-Anweisung in der Skalarfunktion auch innerhalb der einzelnen SELECTs berücksichtigt wird, sodass es keinen Sinn macht, einfach alles zu addieren die Zeiten.

Manuelles Timing

Und schließlich die Timings aus der #temp-Tabelle:

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

Ergebnisse:

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

Weitere interessante Ergebnisse hier:Der Prozedur-Wrapper hatte immer etwas Overhead, obwohl es wirklich subjektiv sein mag, wie signifikant das ist.

Zusammenfassung

Mir ging es hier heute lediglich darum, das neue DMV in Aktion zu zeigen und die Erwartungen richtig zu setzen – einige Leistungsmetriken für Funktionen werden immer noch irreführend sein, und einige werden immer noch überhaupt nicht verfügbar sein (oder zumindest sehr mühsam sein, sie selbst zusammenzusetzen ).

Ich denke jedoch, dass diese neue DMV eines der größten Teile der Abfrageüberwachung abdeckt, die SQL Server zuvor fehlte:dass Skalarfunktionen manchmal unsichtbare Leistungskiller sind, da der einzige zuverlässige Weg, ihre Verwendung zu identifizieren, darin bestand, den Abfragetext zu analysieren, was ist alles andere als idiotensicher. Vergessen Sie die Tatsache, dass Sie dadurch ihre Auswirkungen auf die Leistung nicht isolieren können oder dass Sie wissen müssten, dass Sie im Abfragetext überhaupt nach skalaren UDFs suchen.

Anhang

Ich habe das Skript angehängt:DMExecFunctionStats.zip

Ab CTP1 ist hier außerdem der Satz von Spalten:

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

Spalten derzeit in sys.dm_exec_function_stats