昨日、SQL ServerのCPU使用率が高いと不満を言っていた顧客から電話がありました。 SQL Server 2012 64ビットSEを使用しています。サーバーはWindows Server 2008 R2 Standard、2.20 GHz Intel Xeon(4コア)、16 GB RAMを実行しています。
犯人が実際にSQL Serverであることを確認した後、DMVクエリ here を使用してインスタンスの上位の待機を確認しました。上位2つの待機は、(1)PREEMPTIVE_OS_DELETESECURITYCONTEXT
および(2)SOS_SCHEDULER_YIELD
でした。
[〜#〜] edit [〜#〜]:「トップ待機クエリ」の結果は次のとおりです(誰かが今朝サーバーを再起動しましたが、私の願い):
多くの集中的な計算/変換を行うので、SOS_SCHEDULER_YIELD
を理解できます。ただし、PREEMPTIVE_OS_DELETESECURITYCONTEXT
待機タイプと、それが最も高い理由について、私は非常に興味があります。
この待機タイプについて私が見つけることができる最良の説明/ディスカッションは、 ここ で見つけることができます。それは言及しています:
PREEMPTIVE_OS_待機タイプは、データベースエンジン(通常はWin32 API)を離れ、SQL Serverの外部でさまざまなタスクのコードを実行する呼び出しです。この場合、以前にリモートリソースアクセスに使用されていたセキュリティコンテキストが削除されています。関連するAPIは、実際にはDeleteSecurityContext()という名前です
私の知る限りでは、リンクサーバーやファイルテーブルなどの外部リソースはありません。また、なりすましなどは一切行いません。バックアップが原因でスパイクが発生したり、ドメインコントローラに障害が発生したりする可能性はありますか?
これが主な待機タイプになる原因は何ですか?この待機タイプをさらに追跡するにはどうすればよいですか?
編集2:Windowsセキュリティログの内容を確認しました。興味深いと思われるエントリがいくつかありますが、これらが正常かどうかはわかりません。
Special privileges assigned to new logon.
Subject:
Security ID: NT SERVICE\MSSQLServerOLAPService
Account Name: MSSQLServerOLAPService
Account Domain: NT Service
Logon ID: 0x3143c
Privileges: SeImpersonatePrivilege
Special privileges assigned to new logon.
Subject:
Security ID: NT SERVICE\MSSQLSERVER
Account Name: MSSQLSERVER
Account Domain: NT Service
Logon ID: 0x2f872
Privileges: SeAssignPrimaryTokenPrivilege
SeImpersonatePrivilege
Edit 3:@Jon Seigel、あなたがリクエストしたとおり、これがクエリの結果です。ポールのものとは少し異なります:
編集4:認めます、私は初めて拡張イベントのユーザーです。この待機タイプをwait_info_externalイベントに追加し、数百のエントリを確認しました。 SQLテキストまたはプランハンドルはなく、呼び出しスタックのみです。ソースをさらに追跡するにはどうすればよいですか?
この質問は、タイトルに基づいて、主にPREEMPTIVE_OS_DELETESECURITYCONTEXT待機タイプに関係していることを知っていますが、これは「SQL ServerでのCPU使用率の高さについて不平を言っていたお客様の誤解です」 "。
この特定の待機タイプに焦点を合わせることが野生のガチョウ追跡であると私が思う理由は、接続が確立されるたびに上がるためです。私のラップトップで次のクエリを実行しています(つまり、私が唯一のユーザーです)。
SELECT *
FROM sys.dm_os_wait_stats
WHERE wait_type = N'PREEMPTIVE_OS_DELETESECURITYCONTEXT'
次に、次のいずれかを実行して、このクエリを再実行します。
SQLCMD -E -Q "select 1"
これで、CPUの使用率が高いことがわかったので、何が実行されているかを調べて、CPUの使用率が高いセッションを確認する必要があります。
SELECT req.session_id AS [SPID],
req.blocking_session_id AS [BlockedBy],
req.logical_reads AS [LogReads],
DB_NAME(req.database_id) AS [DatabaseName],
SUBSTRING(txt.[text],
(req.statement_start_offset / 2) + 1,
CASE
WHEN req.statement_end_offset > 0
THEN (req.statement_end_offset - req.statement_start_offset) / 2
ELSE LEN(txt.[text])
END
) AS [CurrentStatement],
txt.[text] AS [CurrentBatch],
CONVERT(XML, qplan.query_plan) AS [StatementQueryPlan],
OBJECT_NAME(qplan.objectid, qplan.[dbid]) AS [ObjectName],
sess.[program_name],
sess.[Host_name],
sess.nt_user_name,
sess.total_scheduled_time,
sess.memory_usage,
req.*
FROM sys.dm_exec_requests req
INNER JOIN sys.dm_exec_sessions sess
ON sess.session_id = req.session_id
CROSS APPLY sys.dm_exec_sql_text(req.[sql_handle]) txt
OUTER APPLY sys.dm_exec_text_query_plan(req.plan_handle,
req.statement_start_offset,
req.statement_end_offset) qplan
WHERE req.session_id <> @@SPID
ORDER BY req.logical_reads DESC, req.cpu_time DESC
--ORDER BY req.cpu_time DESC, req.logical_reads DESC
通常は上記のクエリをそのまま実行しますが、コメント化されているORDER BY句を切り替えて、より興味深い/役立つ結果が得られるかどうかを確認することもできます。
または、dm_exec_query_statsに基づいて次のコマンドを実行して、最もコストの高いクエリを見つけることもできます。以下の最初のクエリは、個々のクエリ(複数のプランがある場合でも)を示し、平均CPU時間で並べ替えられますが、平均論理読み取りに簡単に変更できます。大量のリソースを使用しているように見えるクエリを見つけたら、「sql_handle」と「statement_start_offset」を以下の2番目のクエリのWHERE条件にコピーして、個々のプランを確認します(1以上になる場合があります)。右端までスクロールすると、XMLプランがあったとすると、それが(グリッドモードで)リンクとして表示され、クリックするとプランビューアーに移動します。
クエリ#1:クエリ情報を取得
;WITH cte AS
(
SELECT qstat.[sql_handle],
qstat.statement_start_offset,
qstat.statement_end_offset,
COUNT(*) AS [NumberOfPlans],
SUM(qstat.execution_count) AS [TotalExecutions],
SUM(qstat.total_worker_time) AS [TotalCPU],
(SUM(qstat.total_worker_time * 1.0) / SUM(qstat.execution_count)) AS [AvgCPUtime],
MAX(qstat.max_worker_time) AS [MaxCPU],
SUM(qstat.total_logical_reads) AS [TotalLogicalReads],
(SUM(qstat.total_logical_reads * 1.0) / SUM(qstat.execution_count)) AS [AvgLogicalReads],
MAX(qstat.max_logical_reads) AS [MaxLogicalReads],
SUM(qstat.total_rows) AS [TotalRows],
(SUM(qstat.total_rows * 1.0) / SUM(qstat.execution_count)) AS [AvgRows],
MAX(qstat.max_rows) AS [MaxRows]
FROM sys.dm_exec_query_stats qstat
GROUP BY qstat.[sql_handle], qstat.statement_start_offset, qstat.statement_end_offset
)
SELECT cte.*,
DB_NAME(txt.[dbid]) AS [DatabaseName],
SUBSTRING(txt.[text],
(cte.statement_start_offset / 2) + 1,
CASE
WHEN cte.statement_end_offset > 0
THEN (cte.statement_end_offset - cte.statement_start_offset) / 2
ELSE LEN(txt.[text])
END
) AS [CurrentStatement],
txt.[text] AS [CurrentBatch]
FROM cte
CROSS APPLY sys.dm_exec_sql_text(cte.[sql_handle]) txt
ORDER BY cte.AvgCPUtime DESC
クエリ#2:プラン情報を取得
SELECT *,
DB_NAME(qplan.[dbid]) AS [DatabaseName],
CONVERT(XML, qplan.query_plan) AS [StatementQueryPlan],
SUBSTRING(txt.[text],
(qstat.statement_start_offset / 2) + 1,
CASE
WHEN qstat.statement_end_offset > 0
THEN (qstat.statement_end_offset - qstat.statement_start_offset) / 2
ELSE LEN(txt.[text])
END
) AS [CurrentStatement],
txt.[text] AS [CurrentBatch]
FROM sys.dm_exec_query_stats qstat
CROSS APPLY sys.dm_exec_sql_text(qstat.[sql_handle]) txt
OUTER APPLY sys.dm_exec_text_query_plan(qstat.plan_handle,
qstat.statement_start_offset,
qstat.statement_end_offset) qplan
-- paste info from Query #1 below
WHERE qstat.[sql_handle] = 0x020000001C70C614D261C85875D4EF3C90BD18D02D62453800....
AND qstat.statement_start_offset = 164
-- paste info from Query #1 above
ORDER BY qstat.total_worker_time DESC
SecurityContextは、SQLサーバーによっていくつかの場所で使用されます。名前を付けた1つの例は、リンクサーバーとファイルテーブルです。多分あなたはcmdexecを使用していますか?プロキシアカウントを使用したSQL Serverエージェントジョブ? Webサービスを呼び出しますか?リモートリソースは多くの面白いことができます。
偽装イベントは、Windowsのセキュリティイベントに記録できます。あなたがそこに手掛かりを見つけているかもしれません。さらに、ブラックボックスレコーダー(拡張イベント)を確認することもできます。
これらの待機タイプが新しい(および高CPUに関連する)か、またはサーバーにとって通常のものかを確認しましたか?