Sqlserver
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Sqlserver

Kiểm tra tình trạng máy chủ SQL chủ động, Phần 5:Chờ thống kê

Nhóm SQLskills yêu thích số liệu thống kê về thời gian chờ. Nếu bạn xem qua các bài đăng trên blog này (xem các bài đăng của Paul trên Knee-Jerk Wait Statistics) và trên trang SQLskills, bạn sẽ thấy các bài đăng của tất cả chúng tôi thảo luận về giá trị của số liệu thống kê chờ, những gì chúng tôi tìm kiếm và tại sao lại có một chờ đợi là một vấn đề. Paul viết về điều này nhiều nhất, nhưng tất cả chúng ta thường bắt đầu với số liệu thống kê chờ khi khắc phục sự cố hiệu suất. Điều đó có nghĩa là gì về mặt chủ động?

Để có được bức tranh hoàn chỉnh về ý nghĩa của thống kê chờ trong một vấn đề về hiệu suất, bạn phải biết thời gian chờ bình thường của mình là gì. Điều đó có nghĩa là chủ động nắm bắt thông tin này và sử dụng đường cơ sở đó làm tài liệu tham khảo. Nếu bạn không có dữ liệu này, thì khi sự cố về hiệu suất xảy ra, bạn sẽ không biết liệu các lần chờ PAGELATCH có điển hình trong môi trường của bạn hay không (hoàn toàn có thể xảy ra) hoặc nếu bạn đột nhiên gặp sự cố liên quan đến tempdb do một số mã mới đã được thêm vào .

Dữ liệu thống kê chờ

Trước đây tôi đã xuất bản một tập lệnh mà tôi sử dụng để thu thập số liệu thống kê về thời gian chờ và đó là tập lệnh mà tôi đã sử dụng trong một thời gian dài cho khách hàng. Tuy nhiên, gần đây tôi đã thực hiện các thay đổi đối với tập lệnh của mình và chỉnh sửa một chút phương pháp của mình. Hãy để tôi giải thích tại sao…

Tiền đề cơ bản đằng sau số liệu thống kê chờ là SQL Server đang theo dõi mỗi khi một luồng phải đợi “một cái gì đó”. Đang đợi để đọc một trang từ đĩa? PAGEIOLATCH_XX đợi. Đang chờ được cấp khóa để bạn thực hiện sửa đổi dữ liệu? LCX_M_XXX đợi. Đang chờ cấp bộ nhớ để truy vấn có thể thực thi? RESOURCE_SEMAPHORE đợi. Tất cả những lần chờ này đều được theo dõi trong DMV sys.dm_os_wait_stats và dữ liệu chỉ tích lũy theo thời gian… đó là đại diện tích lũy của những lần chờ.

Ví dụ:tôi có phiên bản SQL Server 2014 trong một trong các máy ảo của tôi đã hoạt động và từ khoảng 9:30 sáng nay:

SELECT [sqlserver_start_time] FROM [sys].[dm_os_sys_info];

Thời gian bắt đầu máy chủ SQL

Bây giờ, nếu tôi muốn xem thống kê chờ đợi của mình trông như thế nào (hãy nhớ, tích lũy cho đến bây giờ) bằng cách sử dụng tập lệnh của Paul, tôi thấy rằng TRACEWRITE là thời gian chờ “tiêu chuẩn” hiện tại của tôi:

Tổng số hiện tại đang chờ

Được rồi, bây giờ chúng ta hãy giới thiệu năm phút tranh luận tạm thời và xem điều đó ảnh hưởng như thế nào đến thống kê chờ tổng thể của tôi. Tôi có một tập lệnh mà Jonathan đã sử dụng trước đây để tạo ra sự tranh chấp tạm thời và tôi đã thiết lập nó để nó chạy trong 5 phút:

USE AdventureWorks2012;
GO
 
SET NOCOUNT ON;
GO
 
DECLARE @CurrentTime SMALLDATETIME = SYSDATETIME(), @EndTime SMALLDATETIME = DATEADD(MINUTE, 5, SYSDATETIME());
WHILE @CurrentTime < @EndTime
BEGIN
  IF OBJECT_ID('tempdb..#temp') IS NOT NULL
  BEGIN
    DROP TABLE #temp;
  END
 
  CREATE TABLE #temp
  (
    ProductID INT PRIMARY KEY,
    OrderQty INT,
    TotalDiscount MONEY,
    LineTotal MONEY,
    Filler NCHAR(500) DEFAULT(N'') NOT NULL
  );
 
  INSERT INTO #temp(ProductID, OrderQty, TotalDiscount, LineTotal)
  SELECT
    sod.ProductID,
    SUM(sod.OrderQty),
    SUM(sod.LineTotal),
    SUM(sod.OrderQty + sod.UnitPriceDiscount)
  FROM Sales.SalesOrderDetail AS sod
  GROUP BY ProductID;
 
  DECLARE
    @ProductNumber NVARCHAR(25),
    @Name NVARCHAR(50),
    @TotalQty INT,
    @SalesTotal MONEY,
    @TotalDiscount MONEY;
 
  SELECT
    @ProductNumber = p.ProductNumber,
    @Name = p.Name,
    @TotalQty = t1.OrderQty,
    @SalesTotal = t1.LineTotal,
    @TotalDiscount = t1.TotalDiscount
  FROM Production.Product AS p
  JOIN #temp AS t1 ON p.ProductID = t1.ProductID;
 
  SET @CurrentTime = SYSDATETIME()
END

Tôi đã sử dụng dấu nhắc lệnh để khởi động 10 phiên chạy tập lệnh này và chạy đồng thời một tập lệnh ghi lại thống kê chờ tổng thể của tôi, ảnh chụp nhanh các lần chờ trong khoảng thời gian 5 phút và sau đó thống kê lại tổng thể. Đầu tiên, một bí mật nhỏ, vì chúng ta luôn bỏ qua các đợi lành tính, nên có thể hữu ích khi nhồi chúng vào một bảng để bạn có thể tham chiếu đến một đối tượng thay vì liên tục phải viết mã danh sách các chuỗi loại trừ trong một truy vấn. Vì vậy:

USE SQLskills_WaitStats;
GO
 
CREATE TABLE dbo.WaitsToIgnore(WaitType SYSNAME PRIMARY KEY);
 
INSERT dbo.WaitsToIgnore(WaitType) VALUES(N'BROKER_EVENTHANDLER'),
  (N'BROKER_RECEIVE_WAITFOR'), (N'BROKER_TASK_STOP'), (N'BROKER_TO_FLUSH'),
  (N'BROKER_TRANSMITTER'),     (N'CHECKPOINT_QUEUE'), (N'CHKPT'),
  (N'CLR_AUTO_EVENT'),         (N'CLR_MANUAL_EVENT'), (N'CLR_SEMAPHORE'),
  (N'DBMIRROR_DBM_EVENT'),     (N'DBMIRROR_EVENTS_QUEUE'),
  (N'DBMIRROR_WORKER_QUEUE'),  (N'DBMIRRORING_CMD'),  (N'DIRTY_PAGE_POLL'),
  (N'DISPATCHER_QUEUE_SEMAPHORE'),  (N'EXECSYNC'),    (N'FSAGENT'),
  (N'FT_IFTS_SCHEDULER_IDLE_WAIT'), (N'FT_IFTSHC_MUTEX'), (N'HADR_CLUSAPI_CALL'),
  (N'HADR_FILESTREAM_IOMGR_IOCOMPLETIO(N'), (N'HADR_LOGCAPTURE_WAIT'),
  (N'HADR_NOTIFICATION_DEQUEUE'), (N'HADR_TIMER_TASK'), (N'HADR_WORK_QUEUE'),
  (N'KSOURCE_WAKEUP'),         (N'LAZYWRITER_SLEEP'),   (N'LOGMGR_QUEUE'),
  (N'ONDEMAND_TASK_QUEUE'),    (N'PWAIT_ALL_COMPONENTS_INITIALIZED'),
  (N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP'), 
  (N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP'), 
  (N'REQUEST_FOR_DEADLOCK_SEARCH'), (N'RESOURCE_QUEUE'), (N'SERVER_IDLE_CHECK'),
  (N'SLEEP_BPOOL_FLUSH'),   (N'SLEEP_DBSTARTUP'), (N'SLEEP_DCOMSTARTUP'),
  (N'SLEEP_MASTERDBREADY'), (N'SLEEP_MASTERMDREADY'), (N'SLEEP_MASTERUPGRADED'),
  (N'SLEEP_MSDBSTARTUP'),   (N'SLEEP_SYSTEMTASK'),    (N'SLEEP_TASK'),
  (N'SLEEP_TEMPDBSTARTUP'), (N'SNI_HTTP_ACCEPT'), (N'SP_SERVER_DIAGNOSTICS_SLEEP'),
  (N'SQLTRACE_BUFFER_FLUSH'), (N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP'),
  (N'SQLTRACE_WAIT_ENTRIES'), (N'WAIT_FOR_RESULTS'), (N'WAITFOR'),
  (N'WAITFOR_TASKSHUTDOW(N'), (N'WAIT_XTP_HOST_WAIT'), 
  (N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG'), (N'WAIT_XTP_CKPT_CLOSE'), 
  (N'XE_DISPATCHER_JOIN'), (N'XE_DISPATCHER_WAIT'), (N'XE_TIMER_EVENT');

Bây giờ chúng tôi đã sẵn sàng để nắm bắt những chờ đợi của mình:

/* Capture the instance start time
 
(in this case, time since waits have been accumulating) */
 
SELECT [sqlserver_start_time] FROM [sys].[dm_os_sys_info];
GO
 
/* Get the current time */
 
SELECT SYSDATETIME() AS [Before Test 1];
 
/* Get aggregate waits until now */
 
WITH [Waits] AS
(
  SELECT
    [wait_type],
    [wait_time_ms] / 1000.0 AS [WaitS],
    ([wait_time_ms] - [signal_wait_time_ms]) / 1000.0 AS [ResourceS],
    [signal_wait_time_ms] / 1000.0 AS [SignalS],
    [waiting_tasks_count] AS [WaitCount],
    100.0 * [wait_time_ms] / SUM ([wait_time_ms]) OVER() AS [Percentage],
    ROW_NUMBER() OVER(ORDER BY [wait_time_ms] DESC) AS [RowNum]
  FROM sys.dm_os_wait_stats
  WHERE [wait_type] NOT IN (SELECT WaitType FROM SQLskills_Waits.WaitsToIgnore)
  AND [waiting_tasks_count] > 0
)
SELECT
  MAX ([W1].[wait_type]) AS [WaitType],
  CAST (MAX ([W1].[WaitS]) AS DECIMAL (16,2)) AS [Wait_S],
  CAST (MAX ([W1].[ResourceS]) AS DECIMAL (16,2)) AS [Resource_S],
  CAST (MAX ([W1].[SignalS]) AS DECIMAL (16,2)) AS [Signal_S],
  MAX ([W1].[WaitCount]) AS [WaitCount],
  CAST (MAX ([W1].[Percentage]) AS DECIMAL (5,2)) AS [Percentage],
  CAST ((MAX ([W1].[WaitS]) / MAX ([W1].[WaitCount])) AS DECIMAL (16,4)) AS [AvgWait_S],
  CAST ((MAX ([W1].[ResourceS]) / MAX ([W1].[WaitCount])) AS DECIMAL (16,4)) AS [AvgRes_S],
  CAST ((MAX ([W1].[SignalS]) / MAX ([W1].[WaitCount])) AS DECIMAL (16,4)) AS [AvgSig_S]
FROM [Waits] AS [W1]
INNER JOIN [Waits] AS [W2]
ON [W2].[RowNum] <= [W1].[RowNum]
GROUP BY [W1].[RowNum]
HAVING SUM ([W2].[Percentage]) - MAX ([W1].[Percentage]) < 95; -- percentage threshold
GO
 
/* Get the current time */
 
SELECT SYSDATETIME() AS [Before Test 2];
 
/* Capture a snapshot of waits over a 5 minute period */
 
IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] WHERE [name] = N'##SQLskillsStats1')
  DROP TABLE [##SQLskillsStats1];
 
IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] WHERE [name] = N'##SQLskillsStats2')
  DROP TABLE [##SQLskillsStats2];
GO
 
SELECT [wait_type], [waiting_tasks_count], [wait_time_ms], 
  [max_wait_time_ms], [signal_wait_time_ms]
INTO ##SQLskillsStats1
FROM sys.dm_os_wait_stats;
GO
 
WAITFOR DELAY '00:05:00';
GO
 
SELECT [wait_type], [waiting_tasks_count], [wait_time_ms],
  [max_wait_time_ms], [signal_wait_time_ms]
INTO ##SQLskillsStats2
FROM sys.dm_os_wait_stats;
GO
 
WITH [DiffWaits] AS
(
  SELECT    -- Waits that weren't in the first snapshot
      [ts2].[wait_type],
      [ts2].[wait_time_ms],
      [ts2].[signal_wait_time_ms],
      [ts2].[waiting_tasks_count]
    FROM [##SQLskillsStats2] AS [ts2]
    LEFT OUTER JOIN [##SQLskillsStats1] AS [ts1]
    ON [ts2].[wait_type] = [ts1].[wait_type]
    WHERE [ts1].[wait_type] IS NULL
    AND [ts2].[wait_time_ms] > 0
  UNION
  SELECT    -- Diff of waits in both snapshots
      [ts2].[wait_type],
      [ts2].[wait_time_ms] - [ts1].[wait_time_ms] AS [wait_time_ms],
      [ts2].[signal_wait_time_ms] - [ts1].[signal_wait_time_ms] AS [signal_wait_time_ms],
      [ts2].[waiting_tasks_count] - [ts1].[waiting_tasks_count] AS [waiting_tasks_count]
    FROM [##SQLskillsStats2] AS [ts2]
    LEFT OUTER JOIN [##SQLskillsStats1] AS [ts1]
    ON [ts2].[wait_type] = [ts1].[wait_type]
    WHERE [ts1].[wait_type] IS NOT NULL
    AND [ts2].[waiting_tasks_count] - [ts1].[waiting_tasks_count] > 0
    AND [ts2].[wait_time_ms] - [ts1].[wait_time_ms] > 0
),
[Waits] AS
(
  SELECT
    [wait_type],
    [wait_time_ms] / 1000.0 AS [WaitS],
    ([wait_time_ms] - [signal_wait_time_ms]) / 1000.0 AS [ResourceS],
    [signal_wait_time_ms] / 1000.0 AS [SignalS],
    [waiting_tasks_count] AS [WaitCount],
    100.0 * [wait_time_ms] / SUM ([wait_time_ms]) OVER() AS [Percentage],
    ROW_NUMBER() OVER(ORDER BY [wait_time_ms] DESC) AS [RowNum]
  FROM [DiffWaits]
  WHERE [wait_type] NOT IN (SELECT WaitType FROM SQLskills_WaitStats.dbo.WaitsToIgnore)
)
SELECT
  [W1].[wait_type] AS [WaitType],
  CAST ([W1].[WaitS] AS DECIMAL (16, 2)) AS [Wait_S],
  CAST ([W1].[ResourceS] AS DECIMAL (16, 2)) AS [Resource_S],
  CAST ([W1].[SignalS] AS DECIMAL (16, 2)) AS [Signal_S],
  [W1].[WaitCount] AS [WaitCount],
  CAST ([W1].[Percentage] AS DECIMAL (5, 2)) AS [Percentage],
  CAST (([W1].[WaitS] / [W1].[WaitCount]) AS DECIMAL (16, 4)) AS [AvgWait_S],
  CAST (([W1].[ResourceS] / [W1].[WaitCount]) AS DECIMAL (16, 4)) AS [AvgRes_S],
  CAST (([W1].[SignalS] / [W1].[WaitCount]) AS DECIMAL (16, 4)) AS [AvgSig_S]
FROM [Waits] AS [W1]
INNER JOIN [Waits] AS [W2]
ON [W2].[RowNum] <= [W1].[RowNum]
GROUP BY [W1].[RowNum], [W1].[wait_type], [W1].[WaitS], 
  [W1].[ResourceS], [W1].[SignalS], [W1].[WaitCount], [W1].[Percentage]
HAVING SUM ([W2].[Percentage]) - [W1].[Percentage] < 95; -- percentage threshold
GO
 
-- Cleanup
 
IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] WHERE [name] = N'##SQLskillsStats1')
  DROP TABLE [##SQLskillsStats1];
 
IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] WHERE [name] = N'##SQLskillsStats2')
  DROP TABLE [##SQLskillsStats2];
GO
 
/* Get the current time */
 
SELECT SYSDATETIME() AS [After Test 1];
 
/* Get aggregate waits again */
 
WITH [Waits] AS
(
  SELECT
    [wait_type],
    [wait_time_ms] / 1000.0 AS [WaitS],
    ([wait_time_ms] - [signal_wait_time_ms]) / 1000.0 AS [ResourceS],
    [signal_wait_time_ms] / 1000.0 AS [SignalS],
    [waiting_tasks_count] AS [WaitCount],
    100.0 * [wait_time_ms] / SUM ([wait_time_ms]) OVER() AS [Percentage],
    ROW_NUMBER() OVER(ORDER BY [wait_time_ms] DESC) AS [RowNum]
  FROM sys.dm_os_wait_stats
  WHERE [wait_type] NOT IN (SELECT WaitType FROM SQLskills_WaitStats.dbo.WaitsToIgnore)
  AND [waiting_tasks_count] > 0
)
SELECT
  MAX ([W1].[wait_type]) AS [WaitType],
  CAST (MAX ([W1].[WaitS]) AS DECIMAL (16,2)) AS [Wait_S],
  CAST (MAX ([W1].[ResourceS]) AS DECIMAL (16,2)) AS [Resource_S],
  CAST (MAX ([W1].[SignalS]) AS DECIMAL (16,2)) AS [Signal_S],
  MAX ([W1].[WaitCount]) AS [WaitCount],
  CAST (MAX ([W1].[Percentage]) AS DECIMAL (5,2)) AS [Percentage],
  CAST ((MAX ([W1].[WaitS]) / MAX ([W1].[WaitCount])) AS DECIMAL (16,4)) AS [AvgWait_S],
  CAST ((MAX ([W1].[ResourceS]) / MAX ([W1].[WaitCount])) AS DECIMAL (16,4)) AS [AvgRes_S],
  CAST ((MAX ([W1].[SignalS]) / MAX ([W1].[WaitCount])) AS DECIMAL (16,4)) AS [AvgSig_S]
FROM [Waits] AS [W1]
INNER JOIN [Waits] AS [W2]
ON [W2].[RowNum] <= [W1].[RowNum]
GROUP BY [W1].[RowNum]
HAVING SUM ([W2].[Percentage]) - MAX ([W1].[Percentage]) < 95; -- percentage threshold
GO
 
/* Get the current time */
 
SELECT SYSDATETIME() AS [After Test 2];

Nếu chúng ta nhìn vào kết quả đầu ra, chúng ta có thể thấy rằng trong khi 10 phiên bản của tập lệnh để tạo tranh chấp tạm thời đang chạy, SOS_SCHEDULER_YIELD là kiểu chờ phổ biến nhất của chúng tôi và chúng tôi cũng có PAGELATCH_XX chờ, như mong đợi:

Tóm tắt thời gian chờ trước, trong và sau khi kiểm tra

Nếu chúng ta nhìn vào số lần chờ trung bình SAU KHI kiểm tra hoàn tất, chúng tôi lại thấy TRACEWRITE là lần chờ cao nhất và chúng tôi thấy SOS_SCHEDULER_YIELD là lần chờ. Tùy thuộc vào những gì khác đang chạy trong môi trường, sự chờ đợi này có thể kéo dài hoặc không kéo dài trong khoảng thời gian chờ đợi hàng đầu của chúng tôi và nó có thể nổi lên hoặc không như một kiểu chờ đợi để điều tra.

Chủ động nắm bắt số liệu thống kê về thời gian chờ

Theo mặc định, thống kê chờ là tích lũy . Có, bạn có thể xóa chúng bất cứ lúc nào bằng cách sử dụng DBCC SQLPERF, nhưng tôi thấy rằng hầu hết mọi người không làm điều đó một cách thường xuyên, họ chỉ để chúng tích lũy. Và điều này là tốt, nhưng hãy hiểu điều đó ảnh hưởng đến dữ liệu của bạn như thế nào. Nếu bạn chỉ khởi động lại phiên bản của mình khi bạn vá lỗi hoặc khi có sự cố (hy vọng là không thường xuyên xảy ra), thì dữ liệu đó có thể được tích lũy trong nhiều tháng. Bạn càng có nhiều dữ liệu, càng khó thấy các biến thể nhỏ… những thứ có thể là vấn đề về hiệu suất. Ngay cả khi bạn gặp “sự cố lớn” ảnh hưởng đến toàn bộ máy chủ của bạn trong vài phút, như chúng tôi đã làm ở đây với tempdb, nó có thể không tạo ra đủ thay đổi trong dữ liệu của bạn để được phát hiện trong dữ liệu tích lũy. Thay vào đó, bạn cần chụp nhanh dữ liệu (chụp lại, đợi vài phút, chụp lại và sau đó thay đổi dữ liệu) để xem điều gì đang thực sự diễn ra ngay bây giờ .

Do đó, nếu bạn chỉ xem nhanh số liệu thống kê chờ vài giờ một lần, thì dữ liệu bạn đã thu thập chỉ hiển thị tổng hợp liên tục theo thời gian. Bạn có thể Khác biệt các ảnh chụp nhanh đó để hiểu rõ về hiệu suất giữa các ảnh chụp nhanh, nhưng tôi có thể nói với bạn rằng bạn không cần phải viết mã này dựa trên một tập dữ liệu lớn, đó là một điều khó khăn (nhưng tôi không phải là nhà phát triển, vì vậy có thể dễ dàng cho bạn ).

Phương pháp thu thập thống kê thời gian chờ truyền thống của tôi là chỉ chụp nhanh sys.dm_os_wait_stats vài giờ một lần bằng cách sử dụng tập lệnh gốc của Paul:

USE [BaselineData];
GO
 
IF NOT EXISTS (SELECT * FROM [sys].[tables] WHERE [name] = N'SQLskills_WaitStats_OldMethod')
BEGIN
  CREATE TABLE [dbo].[SQLskills_WaitStats_OldMethod]
  (
    [RowNum] [bigint] IDENTITY(1,1) NOT NULL,
    [CaptureDate] [datetime] NULL,
    [WaitType] [nvarchar](120) NULL,
    [Wait_S] [decimal](14, 2) NULL,
    [Resource_S] [decimal](14, 2) NULL,
    [Signal_S] [decimal](14, 2) NULL,
    [WaitCount] [bigint] NULL,
    [Percentage] [decimal](4, 2) NULL,
    [AvgWait_S] [decimal](14, 4) NULL,
    [AvgRes_S] [decimal](14, 4) NULL,
    [AvgSig_S] [decimal](14, 4) NULL
  );
 
  CREATE CLUSTERED INDEX [CI_SQLskills_WaitStats_OldMethod] 
    ON [dbo].[SQLskills_WaitStats_OldMethod] ([CaptureDate],[RowNum]);
END
GO
 
/* Query to use in scheduled job */
 
USE [BaselineData];
GO
 
INSERT INTO [dbo].[SQLskills_WaitStats_OldMethod]
(
  [CaptureDate] ,
  [WaitType] ,
  [Wait_S] ,
  [Resource_S] ,
  [Signal_S] ,
  [WaitCount] ,
  [Percentage] ,
  [AvgWait_S] ,
  [AvgRes_S] ,
  [AvgSig_S]
)
EXEC ('WITH [Waits] AS (SELECT
      [wait_type],
      [wait_time_ms] / 1000.0 AS [WaitS],
      ([wait_time_ms] - [signal_wait_time_ms]) / 1000.0 AS [ResourceS],
      [signal_wait_time_ms] / 1000.0 AS [SignalS],
      [waiting_tasks_count] AS [WaitCount],
      100.0 * [wait_time_ms] / SUM ([wait_time_ms]) OVER() AS [Percentage],
      ROW_NUMBER() OVER(ORDER BY [wait_time_ms] DESC) AS [RowNum]
    FROM sys.dm_os_wait_stats
    WHERE [wait_type] NOT IN (SELECT WaitType FROM SQLskills_WaitStats.dbo.WaitsToIgnore)
  )
  SELECT
    GETDATE(),
    [W1].[wait_type] AS [WaitType],
    CAST ([W1].[WaitS] AS DECIMAL(14, 2)) AS [Wait_S],
    CAST ([W1].[ResourceS] AS DECIMAL(14, 2)) AS [Resource_S],
    CAST ([W1].[SignalS] AS DECIMAL(14, 2)) AS [Signal_S],
    [W1].[WaitCount] AS [WaitCount],
    CAST ([W1].[Percentage] AS DECIMAL(4, 2)) AS [Percentage],
    CAST (([W1].[WaitS] / [W1].[WaitCount]) AS DECIMAL (14, 4)) AS [AvgWait_S],
    CAST (([W1].[ResourceS] / [W1].[WaitCount]) AS DECIMAL (14, 4)) AS [AvgRes_S],
    CAST (([W1].[SignalS] / [W1].[WaitCount]) AS DECIMAL (14, 4)) AS [AvgSig_S]
  FROM [Waits] AS [W1]
  INNER JOIN [Waits] AS [W2]
  ON [W2].[RowNum] <= [W1].[RowNum]
  GROUP BY [W1].[RowNum], [W1].[wait_type], [W1].[WaitS], [W1].[ResourceS], 
    [W1].[SignalS], [W1].[WaitCount], [W1].[Percentage]
  HAVING SUM ([W2].[Percentage]) - [W1].[Percentage] < 95;'
);

Sau đó, tôi sẽ xem qua và xem phần đợi ở trên cùng cho mỗi ảnh chụp nhanh, ví dụ:

SELECT [w].[CaptureDate] ,
  [w].[WaitType] ,
  [w].[Percentage] ,
  [w].[Wait_S] ,
  [w].[WaitCount] ,
  [w].[AvgWait_S]
FROM   [dbo].[SQLskills_WaitStats_OldMethod] w
JOIN 
(
  SELECT   MIN([RowNum]) AS [RowNumber] , [CaptureDate]
  FROM     [dbo].[SQLskills_WaitStats_OldMethod]
  WHERE   [CaptureDate] IS NOT NULL
  AND [CaptureDate] > GETDATE() - 60
  GROUP BY [CaptureDate]
) m ON [w].[RowNum] = [m].[RowNumber]
ORDER BY [w].[CaptureDate];

Phương pháp thay thế mới của tôi là thay đổi một vài ảnh chụp nhanh về số liệu thống kê chờ (với hai đến ba phút giữa các lần chụp nhanh) mỗi giờ hoặc lâu hơn. Sau đó, thông tin này cho tôi biết chính xác hệ thống đang chờ gì tại thời điểm đó:

USE [BaselineData];
GO
 
IF NOT EXISTS ( SELECT * FROM   [sys].[tables] WHERE   [name] = N'SQLskills_WaitStats')
BEGIN
  CREATE TABLE [dbo].[SQLskills_WaitStats]
  (
    [RowNum] [bigint] IDENTITY(1,1) NOT NULL,
    [CaptureDate] [datetime] NOT NULL DEFAULT (sysdatetime()),
    [WaitType] [nvarchar](60) NOT NULL,
    [Wait_S] [decimal](16, 2) NULL,
    [Resource_S] [decimal](16, 2) NULL,
    [Signal_S] [decimal](16, 2) NULL,
    [WaitCount] [bigint] NULL,
    [Percentage] [decimal](5, 2) NULL,
    [AvgWait_S] [decimal](16, 4) NULL,
    [AvgRes_S] [decimal](16, 4) NULL,
    [AvgSig_S] [decimal](16, 4) NULL
  ) ON [PRIMARY];
 
  CREATE CLUSTERED INDEX [CI_SQLskills_WaitStats] 
    ON [dbo].[SQLskills_WaitStats] ([CaptureDate],[RowNum]);
END
 
/* Query to use in scheduled job */
 
USE [BaselineData];
GO
 
IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] WHERE [name] = N'##SQLskillsStats1')
  DROP TABLE [##SQLskillsStats1];
 
IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] WHERE [name] = N'##SQLskillsStats2')
  DROP TABLE [##SQLskillsStats2];
GO
 
/* Capture wait stats */
 
SELECT [wait_type], [waiting_tasks_count], [wait_time_ms],
  [max_wait_time_ms], [signal_wait_time_ms]
INTO ##SQLskillsStats1
FROM sys.dm_os_wait_stats;
GO
 
/* Wait some amount of time */
 
WAITFOR DELAY '00:02:00';
GO
 
/* Capture wait stats again */
 
SELECT [wait_type], [waiting_tasks_count], [wait_time_ms],
  [max_wait_time_ms], [signal_wait_time_ms]
INTO ##SQLskillsStats2
FROM sys.dm_os_wait_stats;
GO
 
/* Diff the waits */
 
WITH [DiffWaits] AS
(
  SELECT   -- Waits that weren't in the first snapshot
      [ts2].[wait_type],
      [ts2].[wait_time_ms],
      [ts2].[signal_wait_time_ms],
      [ts2].[waiting_tasks_count]
    FROM [##SQLskillsStats2] AS [ts2]
    LEFT OUTER JOIN [##SQLskillsStats1] AS [ts1]
    ON [ts2].[wait_type] = [ts1].[wait_type]
    WHERE [ts1].[wait_type] IS NULL
    AND [ts2].[wait_time_ms] > 0
  UNION
  SELECT   -- Diff of waits in both snapshots
      [ts2].[wait_type],
      [ts2].[wait_time_ms] - [ts1].[wait_time_ms] AS [wait_time_ms],
      [ts2].[signal_wait_time_ms] - [ts1].[signal_wait_time_ms] AS [signal_wait_time_ms],
      [ts2].[waiting_tasks_count] - [ts1].[waiting_tasks_count] AS [waiting_tasks_count]
    FROM [##SQLskillsStats2] AS [ts2]
    LEFT OUTER JOIN [##SQLskillsStats1] AS [ts1]
    ON [ts2].[wait_type] = [ts1].[wait_type]
    WHERE [ts1].[wait_type] IS NOT NULL
    AND [ts2].[waiting_tasks_count] - [ts1].[waiting_tasks_count] > 0
    AND [ts2].[wait_time_ms] - [ts1].[wait_time_ms] > 0
),
[Waits] AS
(
  SELECT
    [wait_type],
    [wait_time_ms] / 1000.0 AS [WaitS],
    ([wait_time_ms] - [signal_wait_time_ms]) / 1000.0 AS [ResourceS],
    [signal_wait_time_ms] / 1000.0 AS [SignalS],
    [waiting_tasks_count] AS [WaitCount],
    100.0 * [wait_time_ms] / SUM ([wait_time_ms]) OVER() AS [Percentage],
    ROW_NUMBER() OVER(ORDER BY [wait_time_ms] DESC) AS [RowNum]
  FROM [DiffWaits]
  WHERE [wait_type] NOT IN (SELECT WaitType FROM SQLskills_WaitStats.dbo.WaitsToIgnore)
)
INSERT INTO [BaselineData].[dbo].[SQLskills_WaitStats]
(
  [WaitType] ,
  [Wait_S] ,
  [Resource_S] ,
  [Signal_S] ,
  [WaitCount] ,
  [Percentage] ,
  [AvgWait_S] ,
  [AvgRes_S] ,
  [AvgSig_S]
)
SELECT
  [W1].[wait_type],
  CAST ([W1].[WaitS] AS DECIMAL (16, 2)) ,
  CAST ([W1].[ResourceS] AS DECIMAL (16, 2)) ,
  CAST ([W1].[SignalS] AS DECIMAL (16, 2)) ,
  [W1].[WaitCount] ,
  CAST ([W1].[Percentage] AS DECIMAL (5, 2)) ,
  CAST (([W1].[WaitS] / [W1].[WaitCount]) AS DECIMAL (16, 4)) ,
  CAST (([W1].[ResourceS] / [W1].[WaitCount]) AS DECIMAL (16, 4)) ,
  CAST (([W1].[SignalS] / [W1].[WaitCount]) AS DECIMAL (16, 4))
FROM [Waits] AS [W1]
INNER JOIN [Waits] AS [W2]
ON [W2].[RowNum] <= [W1].[RowNum]
GROUP BY [W1].[RowNum], [W1].[wait_type], [W1].[WaitS], [W1].[ResourceS], 
  [W1].[SignalS], [W1].[WaitCount], [W1].[Percentage]
HAVING SUM ([W2].[Percentage]) - [W1].[Percentage] < 95; -- percentage threshold
GO
 
/* Clean up the temp tables */
 
IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] WHERE [name] = N'##SQLskillsStats1')
  DROP TABLE [##SQLskillsStats1];
 
IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] WHERE [name] = N'##SQLskillsStats2')
  DROP TABLE [##SQLskillsStats2];

Phương pháp mới của tôi có tốt hơn không? Tôi nghĩ vậy, vì đây là sự thể hiện tốt hơn về những gì mà hàng chờ đợi trông như thế nào ngay tại thời điểm chụp và nó vẫn đang lấy mẫu theo một khoảng thời gian đều đặn. Đối với cả hai phương pháp, tôi thường xem xét mức chờ đợi cao nhất tại thời điểm chụp:

SELECT [w].[CaptureDate] ,
  [w].[WaitType] ,
  [w].[Percentage] ,
  [w].[Wait_S] ,
  [w].[WaitCount] ,
  [w].[AvgWait_S]
FROM   [dbo].[SQLskills_WaitStats] w
JOIN
(
  SELECT MIN([RowNum]) AS [RowNumber], [CaptureDate]
  FROM     [dbo].[SQLskills_WaitStats]
  WHERE   [CaptureDate] > GETDATE() - 30
  GROUP BY [CaptureDate]
) m 
ON [w].[RowNum] = [m].[RowNumber]
ORDER BY [w].[CaptureDate];

Kết quả:

Chờ đợi nhiều nhất cho mỗi ảnh chụp nhanh (đầu ra mẫu)

Hạn chế tồn tại với tập lệnh gốc của tôi là nó vẫn chỉ là ảnh chụp nhanh . Tôi có thể xác định số lần chờ cao nhất theo thời gian, nhưng nếu có sự cố xảy ra giữa các lần chụp nhanh, thì nó sẽ không hiển thị. Vậy bạn có thể làm gì?

Bạn có thể tăng tần suất chụp của mình. Có lẽ thay vì ghi lại số liệu thống kê chờ đợi mỗi giờ, bạn nắm bắt chúng sau mỗi 15 phút. Hoặc có thể cứ 10 giờ một lần, bạn nắm bắt dữ liệu càng thường xuyên, thì bạn càng có nhiều cơ hội gặp phải vấn đề về hiệu suất.

Tùy chọn khác của bạn sẽ là sử dụng ứng dụng của bên thứ ba, chẳng hạn như SQL Sentry Performance Advisor, để theo dõi các lần chờ. Trình cố vấn hiệu suất lấy cùng một thông tin từ DMV sys.dm_os_wait_stats. Nó truy vấn sys.dm_os_wait_stats cứ 10 giây một lần với một truy vấn rất đơn giản:

SELECT * FROM sys.dm_os_wait_stats WHERE wait_time_ms > 0;

Sau đó, Performance Advisor sẽ lấy dữ liệu này và thêm nó vào cơ sở dữ liệu giám sát của nó. Khi bạn nhìn thấy dữ liệu, các lỗi chờ lành tính sẽ bị xóa và các delta sẽ được tính cho bạn. Ngoài ra, Performance Advisor có màn hình hiển thị tuyệt vời (nhìn vào bảng điều khiển đẹp hơn nhiều so với kết xuất văn bản ở trên) và bạn có thể tùy chỉnh bộ sưu tập nếu muốn. Nếu chúng ta nhìn vào Trình cố vấn hiệu suất và xem dữ liệu từ cả ngày, tôi có thể dễ dàng thấy nơi tôi gặp sự cố trong ngăn SQL Server Waits:

Trang tổng quan của Cố vấn Hiệu suất cho ngày

Và sau đó, tôi có thể đi sâu vào khoảng thời gian đó sau 3 giờ chiều để điều tra thêm điều gì đã xảy ra:

Tìm hiểu kỹ hơn về PA trong sự cố hiệu suất

Tự theo dõi, trừ khi tôi tình cờ chụp nhanh số liệu thống kê chờ cùng một lúc bằng tập lệnh, tôi sẽ bỏ lỡ việc thu thập bất kỳ dữ liệu nào về vấn đề hiệu suất đó. Vì Cố vấn hiệu suất lưu trữ thông tin trong một khoảng thời gian dài, nếu bạn có một chút thành tựu về hiệu suất, bạn thực hiện có sẵn dữ liệu thống kê về thời gian chờ (cùng với nhiều thông tin khác) để giúp nghiên cứu vấn đề và bạn cũng có dữ liệu lịch sử để bạn hiểu những gì bình thường chờ đợi tồn tại trong môi trường của bạn.

Tóm tắt

Dù bạn chọn phương pháp nào để theo dõi các lần chờ đợi, điều quan trọng đầu tiên là bạn phải hiểu cách thức SQL Server lưu trữ thông tin chờ đợi để bạn hiểu dữ liệu bạn đang thấy nếu bạn nắm bắt nó thường xuyên. Nếu bạn phải cuộn các tập lệnh của riêng mình để nắm bắt các khoảng thời gian chờ, bạn bị hạn chế ở chỗ là bạn có thể không nắm bắt được độ lệch dễ dàng như khi sử dụng phần mềm của bên thứ ba. Nhưng điều đó không sao - có một số dữ liệu cơ bản để bạn có thể bắt đầu hiểu thế nào là "bình thường" tốt hơn là không có gì cả . Khi bạn xây dựng kho lưu trữ của mình và bắt đầu làm quen với một môi trường, bạn có thể điều chỉnh các tập lệnh chụp của mình khi cần thiết để giải quyết bất kỳ vấn đề nào có thể tồn tại. Nếu bạn thực sự có lợi ích từ phần mềm của bên thứ ba, hãy sử dụng tối đa thông tin đó và đảm bảo rằng bạn hiểu cách thu thập và lưu trữ các lượt chờ.


  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 Server 2016:Nhập dữ liệu

  2. Làm cách nào để khắc phục lỗi 'Nhà cung cấp đường ống được đặt tên, lỗi 40 - Không thể mở kết nối với' Máy chủ SQL '?

  3. Cách sử dụng Tổng, Trung bình và Đếm trong Câu lệnh Chọn - Hướng dẫn SQL Server / TSQL Phần 128

  4. SQL Server:Cơ sở dữ liệu bị kẹt ở trạng thái Khôi phục

  5. Cách xóa khoảng trắng hàng đầu trong SQL Server - LTRIM ()