Sqlserver
 sql >> база данни >  >> RDS >> Sqlserver

SQL Server 2016 :sys.dm_exec_function_stats

В SQL Server 2016 CTP 2.1 има един нов обект, който се появи след CTP 2.0:sys.dm_exec_function_stats. Това е предназначено да осигури подобна функционалност на sys.dm_exec_procedure_stats, sys.dm_exec_query_stats и sys.dm_exec_trigger_stats. Така че вече е възможно да се проследяват обобщени показатели по време на изпълнение за дефинирани от потребителя функции.

Или е така?

Поне в CTP 2.1 можех да изведа смислени метрики тук само за обикновени скаларни функции – нищо не беше регистрирано за вградени или мултиинструктивни TVF. Не съм изненадан от вградените функции, тъй като те така или иначе са разширени преди изпълнение. Но тъй като TVF с множество оператори често са проблеми с производителността, се надявах и те да се появят. Те все още се появяват в sys.dm_exec_query_stats, така че все още можете да извличате показателите им за ефективност оттам, но може да стане трудно да извършвате агрегирания, когато наистина имате множество изрази, които изпълняват част от работата – нищо не е събрано за вас.

Нека да разгледаме набързо как се случва това. Да приемем, че имаме проста таблица със 100 000 реда:

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;

Исках да сравня какво се случва, когато изследваме скаларни UDF, функции със стойност на таблици с множество оператори и вградени функции с стойност на таблица и как виждаме каква работа е извършена във всеки отделен случай. Първо, представете си нещо тривиално, което можем да направим в SELECT клауза, но може да искаме да разделим, като форматиране на дата като низ:

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

(Присвоявам изхода на променлива, което принуждава цялата таблица да бъде сканирана, но не позволява на показателите за производителност да бъдат повлияни от усилията на SSMS да консумира и изобразява изхода. Благодаря за напомнянето, Mikael Eriksson.)

Много пъти ще видите хора да поставят това преобразуване във функция и то може да бъде скаларно или TVF, като тези:

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

Създадох обвивки на процедури около тези функции, както следва:

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

(И не, dt_ Конвенцията, която виждате, не е нещо ново, което смятам за добра идея, това беше просто най-простият начин, по който можех да изолирам всички тези заявки в DMV от всичко останало, което се събира. Освен това улеснява добавянето на суфикси за лесно разграничаване между заявката в съхранената процедура и ad hoc версията.)

След това създадох таблица #temp за съхраняване на тайминга и повторих този процес (и двете изпълнявайки съхранената процедура два пъти, и изпълнявайки тялото на процедурата като изолирана ad hoc заявка два пъти, и проследяване на времето на всяка една):

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

След това направих някои диагностични заявки и ето резултатите:

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;

Резултати:

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

Това не е печатна грешка; само скаларният UDF показва присъствие в новия 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;

Резултати:

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

Това не е изненадващ резултат:използването на скаларна функция води до намаляване на производителността от порядък по големина, докато TVF с множество изявления е само около 4 пъти по-лош. По време на множество тестове, вградената функция винаги е била толкова бърза или с милисекунда или две по-бърза, отколкото никаква функция.

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;

Съкратени резултати, пренаредени ръчно:

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

Важно нещо, което трябва да се отбележи тук, е, че времето в милисекунди за INSERT в TVF с множество оператори и оператора RETURN в скаларната функция също се отчитат в рамките на отделните SELECT, така че няма смисъл просто да сумирате всички времето.

Ръчни синхронизации

И накрая, времето от таблицата #temp:

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

Резултати:

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

Допълнителни интересни резултати тук:обвивката на процедурата винаги е имала някои допълнителни разходи, въпреки че колко значимо е това може да е наистина субективно.

Резюме

Моята цел тук днес беше просто да покажа новия DMV в действие и да задам правилно очакванията – някои показатели за производителност за функциите все още ще бъдат подвеждащи, а някои все още няма да бъдат достъпни изобщо (или поне ще бъде много досадно да събирате заедно за себе си ).

Мисля обаче, че този нов DMV обхваща една от най-големите части за наблюдение на заявки, които SQL Server липсваше преди:че скаларните функции понякога са невидими убийци на производителността, тъй като единственият надежден начин да се идентифицира тяхното използване е да се анализира текстът на заявката, който далеч не е сигурно. Няма значение факта, че това няма да ви позволи да изолирате тяхното въздействие върху производителността или че на първо място трябва да сте знаели, че търсите скаларни UDF в текста на заявката.

Приложение

Прикачих скрипта:DMExecFunctionStats.zip

Също така, от CTP1, тук е наборът от колони:

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

Колоните, които в момента са в sys.dm_exec_function_stats


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Получаване на резултат от динамичен SQL в променлива за sql-сървър

  2. Инсталиране на клъстер за отказване на SQL сървър -4

  3. Как да създадете ограничение на външния ключ с ON DELETE CASCADE в SQL Server - SQL Server / TSQL урок, част 80

  4. WHERE IN (масив от идентификационни номера)

  5. Кумулативна сума на SQL Server по групи