CPU使用率が高いサーバーのトラブルシューティングを行っています。クエリが実際にはそれを引き起こしていないことがわかった後、コンパイルの調査を開始しました。
パフォーマンスモニターは、毎秒50コンパイル未満および毎秒15再コンパイル未満を示しています。
コンパイルを探すXEセッションを実行した後、毎秒数千のコンパイルが表示されます。
このシステムはトリガーを使用して変更を監査しています。ほとんどのコンパイルはトリガーによるものです。トリガーはsys.dm_tran_active_transactionsを参照します。
私たちの最初の考えは、トリガーでDMVを参照すると毎回コンパイルされるか、またはこの特定のDMVだけが原因である可能性があるということでした。それで私はその理論のテストを始めました。それは毎回コンパイルされますが、DMVを参照せずに値をハードコードするときにトリガーがトリガーされるたびにコンパイルされるかどうかは確認していませんでした。トリガーされるたびにコンパイルされていました。トリガーをドロップすると、コンパイルが停止します。
再現スクリプト:
CREATE TABLE t1 (transaction_id int, Column2 varchar(100));
CREATE TABLE t2 (Column1 varchar(max), Column2 varchar(100));
GO
CREATE TRIGGER t2_ins
ON t2
AFTER INSERT
AS
INSERT INTO t1
SELECT (SELECT TOP 1 transaction_id FROM sys.dm_tran_active_transactions), Column2
FROM inserted;
GO
--Both of these show compilation events
INSERT INTO t2 VALUES ('row1', 'value1');
INSERT INTO t2 VALUES ('row2', 'value2');
GO
ALTER TRIGGER t2_ins
ON t2
AFTER INSERT
AS
INSERT INTO t1
SELECT 1000, Column2
FROM inserted;
GO
--Both of these show compilation events
INSERT INTO t2 VALUES ('row3', 'value3');
INSERT INTO t2 VALUES ('row4', 'value4');
DROP TRIGGER t2_ins;
--These do not show compilation events
INSERT INTO t2 VALUES ('row5', 'value5');
INSERT INTO t2 VALUES ('row6', 'value6');
DROP TABLE t1, t2;
使用されているXEイベントは、トリガーが実際にすべての実行をコンパイルしていると誤解させます。 2つの拡張イベントquery_pre_execution_showplanとquery_post_compilation_showplanがあり、これらは同様の説明がありますが、1つの重要なWordが異なります。
query_pre_execution_showplan
SQLステートメントがコンパイルされた後に発生します。このイベントは、クエリがoptimizedのときに生成される推定クエリプランのXML表現を返します。このイベントを使用すると、パフォーマンスのオーバーヘッドが大きくなる可能性があるため、特定の問題のトラブルシューティングまたは監視を短時間行う場合にのみ使用してください。
query_post_compilation_showplan
SQLステートメントがコンパイルされた後に発生します。このイベントは、クエリがcompiledされたときに生成される推定クエリプランのXML表現を返します。このイベントを使用すると、パフォーマンスのオーバーヘッドが大きくなる可能性があるため、特定の問題のトラブルシューティングまたは監視を短時間行う場合にのみ使用してください。
イベントは説明が完全に同じではなく、再現を使用したその後のテストとは異なるタイミングで発生します。より大きなイベントセッション定義を使用すると、コンパイルが実際に行われている場所を簡単に確認できます。
ここでは、緑色のボックスで自動パラメーター化されている準備済みプランとして、insertステートメントで最初のコンパイルが行われているのを確認できます。トリガーは赤いボックスでコンパイルされ、プランはsp_cache_insertイベントで示されるようにキャッシュに挿入されます。次に、オレンジ色のボックスでトリガーの実行がキャッシュヒットを取得し、バッチの2番目のINSERTステートメントのトリガープランを再利用します。そのため、INSERTコマンドのすべての実行がコンパイルされるわけではなく、sp_cache_hitイベントで確認できるようにプランは再利用されます。トリガー用。
次のイベントに示すように、最初の実行後に2つのINSERTステートメントを個別に再度実行すると、トリガーは再度コンパイルされません。
ここで、最初のステートメントは、キャッシュ内のステートメントの自動パラメーター化されたバージョンのキャッシュヒットを検出しましたが、サブミットされたアドホックバッチのミスを検出しました。イベントの赤いブロックに示すように、トリガーはキャッシュヒットを取得し、再度コンパイルしません。緑のイベントブロックは、別のバッチとして実行される2番目のINSERTステートメントに対してこの動作を繰り返します。ただし、どの場合でも、query_pre_execution_showplanイベントが発生します。これは、optimizedとの違いにのみ起因します。 )compiledがイベントの説明に含まれていますが、これらの一連のイベントで示されているように、トリガーが実行ごとにコンパイルされるわけではありません。
いいえ。トリガーは常に再コンパイルされるわけではありません。ただし、単純なクエリステートメントではプランがキャッシュされないため、常に再コンパイルされます。
挿入または削除された行の数が大幅に変化した場合、トリガーは再コンパイルされます。参照: https://technet.Microsoft.com/en-us/library/ms181055.aspx
XEventsでも同じかどうかはわかりませんが、SQLトレースでは、再コンパイルにイベントサブクラスがあり、再コンパイルされた理由がわかります。それは上記の同じリンクで説明されています。