web-dev-qa-db-ja.com

バッチごとにコンパイルが発生します

T-SQLステートメントをバッチで送信するサードパーティアプリケーションがあります。

データベースは、SQL Server 2016 Enterprise SP1 CU7、16コア、256 GBメモリでホストされています。アドホックの最適化が有効になっています。

これは、実行されているクエリのダミーの例です。

exec sp_executesql N'
IF @@TRANCOUNT = 0 SET TRANSACTION ISOLATION LEVEL SNAPSHOT

select field1, field2 from table1 where field1=@1
option(keep plan, keepfixed, loop join)

select field3, field4 from table2 where field3=@1
option(keep plan, keepfixed, loop join)', N'@1 nvarchar(6)',@1=N'test'

データベースを監視して、毎秒のバッチ数と毎秒のコンパイル数を見ると、それらは常に同じであることがわかります。負荷が高い場合、これは1000バッチ/秒および1000コンパイル/秒になる可能性があります。平均負荷では、150バッチ/秒です。

最近コンパイルされたプランのクエリキャッシュを分析します。

SELECT TOP (1000) qs.creation_time
    , DatabaseName = DB_NAME(st.dbid)
    , qs.execution_count
    , st.text
    , qs.plan_handle
    , qs.sql_handle
    , qs.query_hash 
FROM sys.dm_exec_query_stats qs
    CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st
ORDER BY creation_time DESC;

上記のクエリを実行すると、1秒あたり10〜20の新しいクエリプランしか表示されません。

それはすべてのsp_executesql呼び出しはコンパイルをトリガーしますが、クエリプランはキャッシュされません。

バッチ/秒がコンパイル/秒と同じになる原因は何ですか?

10

すべてのsp_executesql呼び出しがコンパイルをトリガーするようですが、クエリプランはキャッシュされません。

SQL Serverは、sp_executesql呼び出しのみを含むバッチのクエリプランをキャッシュしません。キャッシュされたプランがないと、毎回コンパイルが行われます。これは仕様によるものであり、予想どおりです。

SQL Serverは、コンパイルにかかるコストが低いバッチのキャッシュを回避します。キャッシュされるものとキャッシュされないものの詳細は、長年にわたって何度も変化しています。詳細については、私の回答 トレースフラグ2861および「ゼロコスト」計画の実際の意味 を参照してください。

つまり、特定のパラメータ値を含む再利用の可能性は低く、sp_executesql呼び出しを含むアドホックテキストをコンパイルするコストは非常に小さくなります。 sp_executesqlによって生成された内部のパラメーター化されたバッチはもちろんキャッシュされ、再利用されます-これがその値です。拡張ストアドプロシージャsp_executesql自体もキャッシュされます。

キャッシュして再利用するには、sp_executesqlステートメントを、キャッシュする価値があると見なされるより大きなバッチの一部にする必要があります。例えば:

-- Show compilation counter
SELECT
    DOPC.[object_name],
    DOPC.cntr_value
FROM sys.dm_os_performance_counters AS DOPC
WHERE
    DOPC.counter_name = N'SQL Compilations/sec'
GO
-- This is only here to make the batch worth caching
DECLARE @TC integer =
(
    SELECT TOP (1) @@TRANCOUNT 
    FROM master.dbo.spt_values AS SV
);

-- Example call we are testing
-- (use anything for the inner query, this example uses the Stack Overflow database
EXECUTE sys.sp_executesql 
    N'SELECT LT.Type FROM dbo.LinkTypes AS LT WHERE LT.Id = @id;', 
    N'@id int', 
    @id = 1;
GO
-- Show compilation counter again
SELECT
    DOPC.[object_name],
    DOPC.cntr_value
FROM sys.dm_os_performance_counters AS DOPC
WHERE
    DOPC.counter_name = N'SQL Compilations/sec'

そのコードを数回実行します。ただし、初めて、多くのコンパイルが期待どおりに報告されます。 2回目は、optimize for ad hoc workloadsが有効になっていない限り、コンパイルは報告されません(つまり、Compiled Plan Stubのみがキャッシュされます)。 3回目は、どのスタブも完全にキャッシュされたアドホックプランに昇格されるため、コンパイルは報告されません。

DECLARE @TCステートメントを削除して、sys.sp_executesqlステートメントが実行された回数に関係なく、それがないとキャッシュされないことを確認します。

関連付けられているプラ​​ンキャッシュエントリを表示するには:

-- Show cached plans
SELECT
    DECP.refcounts,
    DECP.usecounts,
    DECP.size_in_bytes,
    DECP.cacheobjtype,
    DECP.objtype,
    DECP.plan_handle,
    DECP.parent_plan_handle,
    DEST.[text]
FROM sys.dm_exec_cached_plans AS DECP
CROSS APPLY sys.dm_exec_sql_text(DECP.plan_handle) AS DEST
WHERE 
    DEST.[text] LIKE N'%sp_executesql%'
    AND DEST.[text] NOT LIKE N'%dm_exec_cached_plans%';

関連するQ&A: トリガーは毎回コンパイルされますか?

12
Paul White 9