SQLskillsチームは、待機統計が大好きです。このブログ(Knee-Jerk Wait Statisticsに関するPaulの投稿を参照)およびSQLskillsサイトの投稿を見ると、待機統計の価値、私たちが探しているもの、および特定の理由について議論している私たち全員からの投稿が表示されます。待つことが問題です。 Paulはこれについて最も多く書いていますが、パフォーマンスの問題をトラブルシューティングするときは、通常、待機統計から始めます。積極的であることという意味ではどういう意味ですか?
パフォーマンスの問題が発生したときの待機統計の意味を完全に把握するには、通常の待機が何であるかを知る必要があります。つまり、この情報を積極的に取得し、そのベースラインを参照として使用することを意味します。このデータがない場合、パフォーマンスの問題が発生したときに、PAGELATCH待機がご使用の環境で一般的であるか(かなり可能)、または追加された新しいコードが原因でtempdbに関連する問題が突然発生したかどうかはわかりません。 。
待機統計データ
以前、待機統計を取得するために使用するスクリプトを公開しました。これは、クライアントのために長い間使用しているスクリプトです。ただし、最近スクリプトに変更を加え、メソッドを少し調整しました。理由を説明させてください…
待機統計の背後にある基本的な前提は、スレッドが「何か」を待機する必要があるたびにSQLServerが追跡していることです。ディスクからページを読み取るのを待っていますか? PAGEIOLATCH_XX待機します。データに変更を加えるためにロックが付与されるのを待っていますか? LCX_M_XXX待機します。クエリを実行できるようにメモリ許可を待っていますか? RESOURCE_SEMAPHORE待機します。これらの待機はすべてsys.dm_os_wait_statsDMVで追跡され、データは時間の経過とともに蓄積されます…これは待機の累積的な代表です。
たとえば、VMの1つにSQL Server 2014インスタンスがあり、今朝9時30分頃から稼働しています。
SELECT [sqlserver_start_time] FROM [sys].[dm_os_sys_info];
SQLServerの開始時刻
ここで、Paulのスクリプトを使用して待機統計がどのように見えるか(これまでの累積を思い出してください)を見ると、TRACEWRITEが現在の「標準」待機であることがわかります。
現在の集計待機
では、5分間のtempdb競合を紹介し、それが全体的な待機統計にどのように影響するかを見てみましょう。 Jonathanが以前にtempdb競合を作成するために使用したスクリプトがあり、5分間実行されるように設定しました:
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
コマンドプロンプトを使用して、このスクリプトを実行する10のセッションを開始し、同時に、全体的な待機統計、5分間の待機のスナップショット、および全体的な待機統計をキャプチャするスクリプトを実行しました。まず、ちょっとした秘密です。良性の待機は常に無視されるため、クエリで除外文字列のリストを常にハードコーディングする代わりに、オブジェクトを参照できるように、それらをテーブルに詰め込むと便利です。だから:
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');
これで、待機をキャプチャする準備が整いました:
/* 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];
出力を見ると、tempdb競合を作成するスクリプトの10個のインスタンスが実行されている間、SOS_SCHEDULER_YIELDが最も一般的な待機タイプであり、予想どおりPAGELATCH_XX待機もあったことがわかります。
テストが完了した後の平均待機時間を見ると、TRACEWRITEが最大の待機として表示され、SOS_SCHEDULER_YIELDが待機として表示されます。環境内で他に何が実行されているかに応じて、この待機は上位の待機に長く続く場合と持続しない場合があり、調査する待機タイプとしてバブルする場合としない場合があります。
待機統計を積極的に取得する
デフォルトでは、待機統計は累積 。はい、DBCC SQLPERFを使用していつでもクリアできますが、ほとんどの人は定期的にクリアするのではなく、単に蓄積させるだけです。これは問題ありませんが、それがデータにどのように影響するかを理解してください。パッチを適用したとき、または問題が発生したときに(できればまれにしか発生しない)インスタンスを再起動するだけの場合、そのデータは数か月間蓄積される可能性があります。データが多いほど、小さな変動を確認するのが難しくなります…パフォーマンスの問題になる可能性があります。ここでtempdbで行ったように、サーバー全体に数分間影響を与える「大きな問題」がある場合でも、累積データで検出されるのに十分なデータの変更が作成されない場合があります。むしろ、データのスナップショットを作成して(キャプチャし、数分待ってから再度キャプチャしてから、データを差分する)、実際に何が起こっているかを確認する必要があります。 。
そのため、待機統計を数時間ごとにスナップショットするだけの場合、収集したデータは、時間の経過に伴う継続的な集計を示しているだけです。 できる スナップショット間のパフォーマンスを理解するためにこれらのスナップショットを比較しますが、大きなデータセットに対してこのコードを記述しなければならないことからあなたに言うことができます、それは苦痛です(しかし私は開発者ではないので、おそらくそれはあなたにとって簡単です-簡単です。
待機統計をキャプチャする従来の方法は、Paulの元のスクリプトを使用して数時間ごとにsys.dm_os_wait_statsのスナップショットを作成することでした。
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;' );
次に、各スナップショットの上部の待機を確認します。例:
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];
私の新しい別の方法は、待機統計の2つのスナップショット(スナップショットの間隔は2〜3分)を1時間程度ごとに比較することです。この情報から、その時点でシステムが何を待っていたかが正確にわかります。
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];
私の新しい方法はより良いですか?そうだと思います。これは、キャプチャ時の待機がどのように見えるかをより適切に表しており、引き続き定期的にサンプリングしているためです。どちらの方法でも、私は通常、キャプチャ時に最大の待機時間があったかどうかを確認します。
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];
結果:
各スナップショットのトップ待機(サンプル出力)
私の元のスクリプトに存在した欠点は、それがまだ単なるスナップショットであるということです。 。時間の経過とともに最大の待機時間をトレンドにすることはできますが、スナップショット間で問題が発生した場合、それは表示されません。では、何ができるでしょうか?
キャプチャの頻度を増やすことができます。おそらく、待機統計を1時間ごとにキャプチャする代わりに、15分ごとにキャプチャします。または、おそらく10ごとです。データをキャプチャする頻度が高いほど、パフォーマンスの問題をトラップする必要がある可能性が高くなります。
もう1つのオプションは、SQL SentryPerformanceAdvisorなどのサードパーティアプリケーションを使用して待機を監視することです。 Performance Advisorは、sys.dm_os_wait_statsDMVからまったく同じ情報を取得します。非常に単純なクエリで、10秒ごとにsys.dm_os_wait_statsにクエリを実行します。
SELECT * FROM sys.dm_os_wait_stats WHERE wait_time_ms > 0;
舞台裏では、Performance Advisorがこのデータを取得し、監視データベースに追加します。データが表示されると、良性の待機が削除され、デルタが計算されます。さらに、Performance Advisorには素晴らしい表示があり(ダッシュボードを見ると上記のテキスト出力よりもはるかに優れています)、必要に応じてコレクションをカスタマイズできます。 Performance Advisorを見て、1日中のデータを見ると、SQLServerの[待機]ペインで問題が発生した場所を簡単に確認できます。
そして、午後3時以降のその期間を掘り下げて、何が起こったのかをさらに調査することができます。
自分で監視している場合、スクリプトを使用して待機統計のスナップショットを同時に作成しない限り、そのパフォーマンスの問題に関するデータをキャプチャできませんでした。 Performance Advisorは情報を長期間保存するため、パフォーマンスが低下した場合は、実行します。 問題の調査に役立つ待機統計データ(および他の多くの情報)を用意し、履歴データも用意して、環境に通常の待機が存在することを理解します。
概要
待機を監視するために選択する方法が何であれ、最初に方法を理解することが重要です。 SQL Serverは待機情報を格納するため、定期的にキャプチャする場合に表示されるデータを理解できます。待機をキャプチャするために独自のスクリプトをロールする必要がある場合、サードパーティのソフトウェアを使用する場合ほど簡単に逸脱をキャプチャできない可能性があるという制限があります。しかし、それは問題ありません。ある程度のベースラインデータがあるので、何が「正常」であるかを理解し始めることができます。何も持たないよりも優れています 。リポジトリを構築して環境に慣れてきたら、必要に応じてキャプチャスクリプトを調整して、存在する可能性のある問題を解決できます。サードパーティのソフトウェアを利用できる場合は、その情報を最大限に活用し、待機がどのように収集および保存されているかを理解してください。