Sqlserver
 sql >> Teknologi Basis Data >  >> RDS >> Sqlserver

Pemeriksaan Kesehatan SQL Server Proaktif, Bagian 5:Statistik Tunggu

Tim SQLskills menyukai statistik menunggu. Jika Anda melihat melalui posting di blog ini (lihat posting Paul di Statistik Tunggu Knee-Jerk) dan di situs SQLskills, Anda akan melihat posting dari kita semua yang membahas nilai statistik menunggu, apa yang kita cari, dan mengapa tertentu menunggu adalah masalah. Paul paling sering menulis tentang ini, tetapi kita semua biasanya mulai dengan statistik menunggu saat memecahkan masalah kinerja. Apa yang dimaksud dengan proaktif?

Untuk mendapatkan gambaran lengkap tentang apa arti statistik menunggu selama masalah kinerja, Anda harus tahu apa itu menunggu normal. Itu berarti secara proaktif menangkap informasi ini dan menggunakan garis dasar itu sebagai referensi. Jika Anda tidak memiliki data ini, maka ketika masalah kinerja terjadi, Anda tidak akan tahu apakah PAGELATCH menunggu adalah tipikal di lingkungan Anda (sangat mungkin) atau jika Anda tiba-tiba memiliki masalah terkait tempdb karena beberapa kode baru yang ditambahkan .

Data Statistik Tunggu

Saya sebelumnya telah menerbitkan skrip yang saya gunakan untuk menangkap statistik tunggu, dan ini adalah skrip yang telah lama saya gunakan untuk klien. Namun, saya baru-baru ini membuat perubahan pada skrip saya dan sedikit mengubah metode saya. Biar saya jelaskan kenapa…

Premis mendasar di balik statistik tunggu adalah bahwa SQL Server melacak setiap kali utas harus menunggu "sesuatu." Menunggu untuk membaca halaman dari disk? PAGEIOLATCH_XX tunggu. Menunggu untuk diberikan kunci sehingga Anda melakukan modifikasi pada data? LCX_M_XXX tunggu. Menunggu hibah memori sehingga kueri dapat dieksekusi? RESOURCE_SEMAPHORE tunggu. Semua penantian ini dilacak di sys.dm_os_wait_stats DMV, dan data bertambah seiring waktu… ini adalah perwakilan kumulatif dari penantian.

Misalnya, saya memiliki instance SQL Server 2014 di salah satu VM saya yang telah aktif dan sejak sekitar pukul 09:30 pagi ini:

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

Waktu mulai SQL Server

Sekarang jika saya melihat seperti apa statistik menunggu saya (ingat, kumulatif sampai sekarang) menggunakan skrip Paul, saya melihat bahwa TRACEWRITE adalah "standar" saya saat ini menunggu:

Agregat saat ini menunggu

Oke, sekarang mari kita perkenalkan lima menit pertengkaran tempdb, dan lihat bagaimana hal itu memengaruhi statistik tunggu saya secara keseluruhan. Saya memiliki skrip yang telah digunakan Jonathan sebelumnya untuk membuat pertikaian tempdb, dan saya telah menyiapkannya agar berjalan selama 5 menit:

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

Saya menggunakan prompt perintah untuk memulai 10 sesi yang menjalankan skrip ini, dan secara bersamaan menjalankan skrip yang menangkap statistik menunggu saya secara keseluruhan, snapshot dari menunggu selama periode waktu 5 menit, dan kemudian statistik menunggu keseluruhan lagi. Pertama, sebuah rahasia kecil, karena kami mengabaikan penantian jinak sepanjang waktu, akan berguna untuk memasukkannya ke dalam tabel sehingga Anda dapat mereferensikan suatu objek daripada terus-menerus harus membuat hard-code daftar string pengecualian dalam kueri. Jadi:

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');

Sekarang kami siap untuk menangkap penantian kami:

/* 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];

Jika kita melihat outputnya, kita dapat melihat bahwa sementara 10 contoh skrip untuk membuat pertikaian tempdb sedang berjalan, SOS_SCHEDULER_YIELD adalah jenis menunggu yang paling umum, dan kita juga memiliki PAGELATCH_XX menunggu, seperti yang diharapkan:

Menunggu Ringkasan Sebelum, Selama, dan Setelah Pengujian

Jika kita melihat rata-rata menunggu SETELAH tes selesai, kita kembali melihat TRACEWRITE sebagai menunggu tertinggi, dan kita melihat SOS_SCHEDULER_YIELD sebagai menunggu. Bergantung pada apa lagi yang berjalan di lingkungan, penantian ini mungkin atau mungkin tidak bertahan lama di penantian teratas kami, dan mungkin atau mungkin tidak muncul sebagai jenis tunggu untuk diselidiki.

Mencatat Statistik Menunggu Secara Proaktif

Secara default, statistik tunggu kumulatif . Ya, Anda dapat menghapusnya kapan saja menggunakan DBCC SQLPERF, tetapi saya menemukan bahwa kebanyakan orang tidak melakukannya secara teratur, mereka hanya membiarkannya menumpuk. Dan ini baik-baik saja, tetapi pahami bagaimana hal itu memengaruhi data Anda. Jika Anda hanya memulai ulang instans saat Anda menambalnya, atau saat ada masalah (yang diharapkan jarang terjadi), maka data tersebut dapat terakumulasi selama berbulan-bulan. Semakin banyak data yang Anda miliki, semakin sulit untuk melihat variasi kecil… hal-hal yang dapat menjadi masalah kinerja. Bahkan ketika Anda memiliki "masalah besar" yang memengaruhi seluruh server Anda selama beberapa menit, seperti yang kami lakukan di sini dengan tempdb, itu mungkin tidak cukup membuat perubahan pada data Anda untuk terdeteksi dalam data yang terakumulasi. Sebaliknya, Anda perlu memotret data (menangkapnya, menunggu beberapa menit, merekamnya lagi, lalu membedakan datanya) untuk melihat apa yang sebenarnya terjadi sekarang .

Dengan demikian, jika Anda hanya memotret statistik tunggu setiap beberapa jam, maka data yang Anda kumpulkan hanya menunjukkan agregasi lanjutan dari waktu ke waktu. Anda bisa bedakan snapshot itu untuk mendapatkan pemahaman tentang kinerja di antara snapshot, tetapi saya dapat memberi tahu Anda karena harus menulis kode ini terhadap kumpulan data yang besar, itu menyebalkan (tapi saya bukan pengembang, jadi mungkin mudah bagi Anda ).

Metode tradisional saya untuk menangkap statistik tunggu adalah dengan hanya memotret sys.dm_os_wait_stats setiap beberapa jam menggunakan skrip asli 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;'
);

Saya kemudian akan melewati dan melihat bagian atas menunggu untuk setiap snapshot, misalnya:

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];

Metode alternatif baru saya adalah dengan membedakan beberapa snapshot statistik tunggu (dengan dua hingga tiga menit di antara snapshot) setiap jam atau lebih. Informasi ini kemudian memberi tahu saya apa yang sebenarnya ditunggu oleh sistem pada saat itu:

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];

Apakah metode baru saya lebih baik? Saya rasa begitu, karena ini adalah representasi yang lebih baik dari apa yang terlihat seperti menunggu tepat pada saat pengambilan, dan itu masih mengambil sampel secara berkala. Untuk kedua metode tersebut, saya biasanya mencari waktu tunggu tertinggi pada saat pengambilan:

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];

Hasil:

Tunggu paling atas untuk setiap cuplikan (keluaran sampel)

Kekurangannya, yang ada pada skrip asli saya, adalah masih hanya cuplikan . Saya dapat membuat tren menunggu tertinggi dari waktu ke waktu, tetapi jika ada masalah yang terjadi di antara snapshot, itu tidak akan muncul. Jadi apa yang bisa kamu lakukan?

Anda dapat meningkatkan frekuensi tangkapan Anda. Mungkin alih-alih menangkap statistik menunggu setiap jam, Anda menangkapnya setiap 15 menit. Atau mungkin setiap 10. Semakin sering Anda mengambil data, semakin besar peluang Anda untuk menjebak masalah kinerja.

Pilihan Anda yang lain adalah menggunakan aplikasi pihak ketiga, seperti SQL Sentry Performance Advisor, untuk memantau waktu tunggu. Performance Advisor mengambil informasi yang sama persis dari sys.dm_os_wait_stats DMV. Ini menanyakan sys.dm_os_wait_stats setiap 10 detik dengan permintaan yang sangat sederhana:

SELECT * FROM sys.dm_os_wait_stats WHERE wait_time_ms > 0;

Di balik layar, Performance Advisor kemudian mengambil data ini dan menambahkannya ke database pemantauannya. Saat Anda melihat data, waktu tunggu yang tidak berbahaya dihapus, dan delta dihitung untuk Anda. Selain itu, Performance Advisor memiliki tampilan yang fantastis (melihat dasbor jauh lebih bagus daripada output teks di atas) dan Anda dapat menyesuaikan koleksi jika Anda mau. Jika kita melihat Performance Advisor dan melihat data sepanjang hari, saya dapat dengan mudah melihat di mana saya mengalami masalah di panel SQL Server Waits:

Dasbor Performance Advisor hari ini

Dan saya kemudian dapat menelusuri periode waktu tersebut setelah pukul 15:00 untuk menyelidiki lebih lanjut apa yang terjadi:

Lihat PA selama masalah kinerja

Memantau sendiri, kecuali jika saya memotret statistik tunggu pada saat yang sama dengan skrip, saya akan melewatkan pengambilan data apa pun tentang masalah kinerja itu. Karena Performance Advisor menyimpan informasi untuk jangka waktu yang lama, jika kinerja Anda kurang baik, Anda melakukannya memiliki data statistik menunggu (bersama dengan banyak informasi lain) yang tersedia untuk membantu meneliti masalah ini, dan Anda juga memiliki data historis sehingga Anda memahami apa yang menunggu normal yang ada di lingkungan Anda.

Ringkasan

Metode apa pun yang Anda pilih untuk memantau waktu tunggu, pertama-tama penting untuk memahami bagaimana SQL Server menyimpan informasi tunggu, sehingga Anda memahami data yang Anda lihat jika Anda merekamnya secara teratur. Jika Anda harus menggulung skrip Anda sendiri untuk menangkap waktu tunggu, Anda terbatas karena Anda mungkin tidak menangkap penyimpangan semudah mungkin dengan perangkat lunak pihak ketiga. Tapi tidak apa – memiliki sejumlah data dasar sehingga Anda dapat mulai memahami apa yang "normal" lebih baik daripada tidak memiliki sama sekali . Saat Anda membangun repositori Anda dan mulai terbiasa dengan lingkungan, Anda dapat menyesuaikan skrip tangkapan Anda seperlunya untuk memecahkan masalah apa pun yang mungkin ada. Jika Anda memang mendapatkan manfaat dari perangkat lunak pihak ketiga, gunakan informasi itu sepenuhnya, dan pastikan Anda memahami cara menunggu dikumpulkan dan disimpan.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. 5 Fakta Teratas untuk Menemukan dan Mengganti Teks SQL di SQL Server dengan Fungsi REPLACE

  2. Bisakah saya menetapkan skema default untuk dalam prosedur tersimpan?

  3. Menghitung Running Total dengan OVER Clause dan PARTITION BY Clause di SQL Server

  4. grup sql hanya dengan baris yang berurutan

  5. Cara Mengonversi Nilai Tanggal/Waktu menjadi String di SQL Server menggunakan CONVERT()