Trong SQL Server 2016 CTP 2.1, có một đối tượng mới xuất hiện sau CTP 2.0:sys.dm_exec_osystem_stats. Điều này nhằm cung cấp chức năng tương tự cho sys.dm_exec_procedure_stats, sys.dm_exec_query_stats và sys.dm_exec_trigger_stats. Vì vậy, giờ đây có thể theo dõi chỉ số thời gian chạy tổng hợp cho các chức năng do người dùng xác định.
Hay là nó?
Ít nhất trong CTP 2.1, tôi chỉ có thể lấy được bất kỳ số liệu có ý nghĩa nào ở đây cho các hàm vô hướng thông thường - không có gì được đăng ký cho TVF nội tuyến hoặc nhiều câu lệnh. Tôi không ngạc nhiên về các hàm nội tuyến, vì dù sao thì chúng cũng được mở rộng trước khi thực thi. Nhưng vì các TVF nhiều tuyên bố thường là những vấn đề về hiệu suất, tôi đã hy vọng chúng cũng sẽ xuất hiện. Chúng vẫn xuất hiện trong sys.dm_exec_query_stats, vì vậy bạn vẫn có thể lấy số liệu hiệu suất của chúng từ đó, nhưng có thể khó thực hiện tổng hợp khi bạn thực sự có nhiều câu lệnh thực hiện một số phần của công việc - không có gì được tổng hợp cho bạn.
Chúng ta hãy xem nhanh cách điều này diễn ra. Giả sử chúng ta có một bảng đơn giản với 100.000 hàng:
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;
Tôi muốn so sánh điều gì sẽ xảy ra khi chúng tôi điều tra các UDF vô hướng, các hàm có giá trị bảng đa câu lệnh và các hàm có giá trị bảng nội tuyến và cách chúng tôi xem công việc đã được thực hiện trong từng trường hợp như thế nào. Đầu tiên, hãy tưởng tượng một điều gì đó nhỏ nhặt mà chúng ta có thể làm trong SELECT
, nhưng chúng tôi có thể muốn chia nhỏ kích thước, chẳng hạn như định dạng ngày dưới dạng chuỗi:
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
(Tôi gán kết quả đầu ra cho một biến, điều này buộc toàn bộ bảng phải được quét, nhưng ngăn các chỉ số hiệu suất không bị ảnh hưởng bởi nỗ lực của SSMS trong việc sử dụng và hiển thị kết quả. Cảm ơn lời nhắc, Mikael Eriksson.)
Rất nhiều lần bạn sẽ thấy mọi người đặt chuyển đổi đó vào một hàm và nó có thể là vô hướng hoặc TVF, như sau:
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
Tôi đã tạo các trình bao bọc thủ tục xung quanh các hàm này như sau:
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
(Và không, dt_
quy ước bạn đang thấy không phải là một số điều mới mẻ, tôi nghĩ đó là một ý tưởng hay, đó chỉ là cách đơn giản nhất tôi có thể tách tất cả các truy vấn này trong DMV khỏi mọi thứ khác đang được thu thập. Nó cũng giúp dễ dàng thêm các hậu tố để dễ dàng phân biệt giữa truy vấn bên trong thủ tục được lưu trữ và phiên bản đặc biệt.)
Tiếp theo, tôi tạo bảng #temp để lưu trữ thời gian và lặp lại quá trình này (vừa thực hiện thủ tục được lưu trữ hai lần, vừa thực thi phần thân của thủ tục dưới dạng truy vấn đặc biệt riêng biệt hai lần và theo dõi thời gian của mỗi lần):
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
Sau đó, tôi chạy một số truy vấn chẩn đoán và đây là kết quả:
sys.dm_exec_ Chức năng_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;
Kết quả:
name execution_count time_milliseconds --------- --------------- ----------------- dt_Scalar 400000 1116
Đó không phải là một lỗi đánh máy; chỉ UDF vô hướng mới hiển thị bất kỳ sự hiện diện nào trong DMV mới.
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;
Kết quả:
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
Đây không phải là một kết quả đáng ngạc nhiên:sử dụng hàm vô hướng dẫn đến hình phạt hiệu suất theo thứ tự độ lớn, trong khi TVF đa câu lệnh chỉ kém hơn khoảng 4 lần. Qua nhiều lần kiểm tra, chức năng nội tuyến luôn nhanh hơn hoặc nhanh hơn một phần nghìn giây so với không có chức năng nào.
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;
Kết quả bị cắt bớt, được sắp xếp lại theo cách thủ công:
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
Một điều quan trọng cần lưu ý ở đây là thời gian tính bằng mili giây cho câu lệnh INSERT trong TVF đa câu lệnh và câu lệnh RETURN trong hàm vô hướng cũng được tính trong các câu lệnh SELECT riêng lẻ, vì vậy không có ý nghĩa gì nếu chỉ cộng tất cả thời gian.
Định giờ thủ công
Và cuối cùng là thời gian từ bảng #temp:
SELECT query = q, time_milliseconds = DATEDIFF(millisecond, s, e) FROM #t ORDER BY ID;
Kết quả:
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
Kết quả thú vị bổ sung ở đây:trình bao bọc thủ tục luôn có một số chi phí, mặc dù mức độ quan trọng của điều đó có thể thực sự chủ quan.
Tóm tắt
Quan điểm của tôi ở đây hôm nay chỉ là để hiển thị DMV mới đang hoạt động và đặt kỳ vọng một cách chính xác - một số chỉ số hiệu suất cho các chức năng sẽ vẫn gây hiểu nhầm và một số sẽ vẫn không có sẵn (hoặc ít nhất là rất tẻ nhạt để tự ghép lại với nhau cho chính bạn ).
Tôi nghĩ rằng DMV mới này bao gồm một trong những phần quan trọng nhất của việc giám sát truy vấn mà SQL Server đã thiếu trước đây:rằng các hàm vô hướng đôi khi là những kẻ giết hiệu suất vô hình, bởi vì cách đáng tin cậy duy nhất để xác định việc sử dụng chúng là phân tích cú pháp văn bản truy vấn. còn lâu mới an toàn. Đừng bận tâm đến thực tế là điều đó sẽ không cho phép bạn tách biệt tác động của chúng đối với hiệu suất hoặc bạn phải biết là đang tìm kiếm các UDF vô hướng trong văn bản truy vấn ngay từ đầu.
Phụ lục
Tôi đã đính kèm tập lệnh:DMExecFunctionStats.zip
Ngoài ra, kể từ CTP1, đây là tập hợp các cột:
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 |
Các cột hiện ở sys.dm_exec_osystem_stats