sql >> Databáze >  >> RDS >> Sqlserver

SQL Server 2016:sys.dm_exec_function_stats

V SQL Server 2016 CTP 2.1 je jeden nový objekt, který se objevil po CTP 2.0:sys.dm_exec_function_stats. To je určeno k poskytování podobných funkcí jako sys.dm_exec_procedure_stats, sys.dm_exec_query_stats a sys.dm_exec_trigger_stats. Nyní je tedy možné sledovat agregované runtime metriky pro uživatelem definované funkce.

Nebo ano?

Přinejmenším v CTP 2.1 jsem zde mohl odvodit jakékoli smysluplné metriky pouze pro běžné skalární funkce – nic nebylo registrováno pro inline nebo multipříkazové TVF. Inline funkce mě nepřekvapují, protože jsou v podstatě před spuštěním stejně rozšířeny. Ale protože vícepříkazové TVF jsou často problémy s výkonem, doufal jsem, že se také objeví. Stále se objevují v sys.dm_exec_query_stats, takže odtamtud můžete stále odvodit jejich metriky výkonu, ale může být obtížné provádět agregace, když opravdu máte více příkazů, které provádějí určitý podíl práce – nic pro vás není shrnuto.

Pojďme se rychle podívat, jak to dopadá. Řekněme, že máme jednoduchou tabulku se 100 000 řádky:

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;

Chtěl jsem porovnat, co se stane, když prozkoumáme skalární UDF, vícepříkazové funkce s tabulkovou hodnotou a funkce s inline tabulkou, a jak vidíme, jaká práce byla v každém případě vykonána. Nejprve si představte něco triviálního, co můžeme udělat v SELECT klauzuli, ale můžeme ji chtít rozdělit, jako je formátování data jako řetězce:

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

(Výstup přiřadím proměnné, která vynutí skenování celé tabulky, ale zabrání tomu, aby byly metriky výkonu ovlivněny snahou SSMS spotřebovat a vykreslit výstup. Díky za připomenutí, Mikael Eriksson.)

Mnohokrát uvidíte, jak lidé převádějí tuto konverzi do funkce, a může to být skalární nebo TVF, jako jsou tyto:

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

Vytvořil jsem obálky procedur kolem těchto funkcí následovně:

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

(A ne, dt_ konvence, kterou vidíte, není nějaká nová věc, kterou považuji za dobrý nápad, byl to jen ten nejjednodušší způsob, jak jsem mohl izolovat všechny tyto dotazy v DMV od všeho ostatního, co se shromažďuje. Také to usnadnilo připojování přípon pro snadné rozlišení mezi dotazem v uložené proceduře a verzí ad hoc.)

Dále jsem vytvořil #temp tabulku pro ukládání časování a opakoval tento proces (jak dvakrát spustit uloženou proceduru, tak dvakrát spustit tělo procedury jako izolovaný ad hoc dotaz a sledovat načasování každého z nich):

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

Potom jsem provedl nějaké diagnostické dotazy a zde byly výsledky:

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;

Výsledky:

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

To není překlep; pouze skalární UDF vykazuje jakoukoli přítomnost v novém 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;

Výsledky:

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

To není překvapivý výsledek:použití skalární funkce vede k řádové penalizaci výkonu, zatímco vícepříkazová TVF byla jen asi 4x horší. Během několika testů byla inline funkce vždy stejně rychlá nebo o milisekundu nebo dvě rychlejší než žádná funkce.

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;

Zkrácené výsledky, přeřazené ručně:

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

Zde je důležité poznamenat, že čas v milisekundách pro INSERT ve vícepříkazovém TVF a příkaz RETURN ve skalární funkci se také počítá v rámci jednotlivých SELECTů, takže nemá smysl jen sčítat všechny načasování.

Ruční časování

A nakonec, časování z tabulky #temp:

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

Výsledky:

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

Další zajímavé výsledky zde:obálka procedur měla vždy určitou režii, i když její význam může být skutečně subjektivní.

Shrnutí

Chtěl jsem zde dnes pouze ukázat nový DMV v akci a správně nastavit očekávání – některé výkonnostní metriky funkcí budou stále zavádějící a některé stále nebudou dostupné vůbec (nebo bude přinejmenším velmi zdlouhavé seskládat pro sebe ).

Myslím si, že tento nový DMV pokrývá jednu z největších částí monitorování dotazů, které SQL Server předtím postrádal:že skalární funkce jsou někdy neviditelnými zabijáky výkonu, protože jediným spolehlivým způsobem, jak identifikovat jejich použití, bylo analyzovat text dotazu, který je daleko od spolehlivého. Nevadí, že vám to nedovolí izolovat jejich dopad na výkon, nebo že byste museli vědět, že v textu dotazu hledáte skalární UDF.

Příloha

Připojil jsem skript:DMExecFunctionStats.zip

Od CTP1 je zde také sada sloupců:

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

Sloupce aktuálně v sys.dm_exec_function_stats


  1. MySQL – Jak generovat náhodné číslo

  2. KNIME

  3. Co znamená vybrat počet(1) z název_tabulky na libovolné databázové tabulce?

  4. Jak zkontrolovat nastavení konfigurace pro databázovou poštu na serveru SQL Server (T-SQL)