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


10

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呼び出しがコンパイルをトリガーするようですが、クエリプランはキャッシュされません。

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

回答:


12

それはすべての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は、有効にしない限り、コンパイルは報告されません(したがって、コンパイルされたプランスタブのみがキャッシュされます)。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:トリガーは毎回コンパイルされますか?


11

以下で詳しく説明するように、テストとして別のクエリウィンドウでいくつかのバッチを実行しながら、パフォーマンスモニターとアクティビティモニターでSQL Compilations/secBatch Requests/secに表示されるものを概算できます。

クエリウィンドウ1:

DECLARE @t1 datetime;
DECLARE @t2 datetime;
DECLARE @CompVal1 int;
DECLARE @CompVal2 int;
DECLARE @ReCompVal1 int;
DECLARE @ReCompVal2 int;
DECLARE @BatchVal1 int;
DECLARE @BatchVal2 int;
DECLARE @ElapsedMS decimal(10,2);

SELECT @t1 = GETDATE()
    , @CompVal1 = (
        SELECT spi.cntr_value
        FROM sys.sysperfinfo spi
        WHERE spi.counter_name = 'SQL Compilations/sec                                                                                                            '
        )
    , @ReCompVal1 = (
        SELECT spi.cntr_value
        FROM sys.sysperfinfo spi
        WHERE spi.counter_name = 'SQL Re-Compilations/sec                                                                                                         '
        )
    , @BatchVal1 = (
        SELECT spi.cntr_value
        FROM sys.sysperfinfo spi
        WHERE spi.counter_name = 'Batch Requests/sec                                                                                                              '
        );

WAITFOR DELAY '00:00:10.000';

SELECT @t2 = GETDATE()
    , @CompVal2 = (
        SELECT spi.cntr_value
        FROM sys.sysperfinfo spi
        WHERE spi.counter_name = 'SQL Compilations/sec                                                                                                            '
        )
    , @ReCompVal2 = (
        SELECT spi.cntr_value
        FROM sys.sysperfinfo spi
        WHERE spi.counter_name = 'SQL Re-Compilations/sec                                                                                                         '
        )
    , @BatchVal2 = (
        SELECT spi.cntr_value
        FROM sys.sysperfinfo spi
        WHERE spi.counter_name = 'Batch Requests/sec                                                                                                              '
        );

SET @ElapsedMS = DATEDIFF(MILLISECOND, @t1, @t2);
SELECT  ElapsedTimeMS = @ElapsedMS
    , [SQL Compilations/sec] = (@CompVal2 - @CompVal1) / @ElapsedMS * 1000 
    , [SQL Recompilations/sec] = (@ReCompVal2 - @ReCompVal1) / @ElapsedMS * 1000
    , [Batch Requests/sec] = (@BatchVal2 - @BatchVal1) / @ElapsedMS * 1000;

クエリウィンドウ2で、上記のコードの実行中に次を実行します。コードは単に100個のT-SQLバッチを実行します。

EXEC sys.sp_executesql N'SELECT TOP(1) o.name FROM sys.objects o;';
GO 100

クエリウィンドウ1に戻ると、次のように表示されます。

╔═══════════════╦══════════════════════╦══════════ ══════════════╦════════════════════╗
║ElapsedTimeMS║SQLコンパイル/秒║SQL再コンパイル/秒║バッチリクエスト/秒║
╠═══════════════╬══════════════════════╬══════════ ══════════════╬════════════════════╣
║10020.00║10.07984031000║0.00000000000║10.07984031000║
╚═══════════════╩══════════════════════╩══════════ ══════════════╩════════════════════╝

このクエリを見てみると、

SELECT dest.text
    , deqs.execution_count
FROM sys.dm_exec_query_stats deqs
    CROSS APPLY sys.dm_exec_sql_text(deqs.plan_handle) dest
WHERE dest.text LIKE 'SELECT TOP(1)%'

テストクエリが100回実行されたことを確認できます。

上記の結果では、ステートメントが実行されるたびにコンパイルが行われていることがわかりますsp_executesql。その計画は確かにキャッシュされていますが、そのためのコンパイルが見られます。何が出るの?

マイクロソフトドキュメントでは、これをについて言いますsp_executesql

sp_executesqlは、バッチ、名前のスコープ、およびデータベースコンテキストに関して、EXECUTEと同じ動作をします。sp_executesql @stmtパラメータのTransact-SQLステートメントまたはバッチは、sp_executesqlステートメントが実行されるまでコンパイルされません。次に、@ stmtの内容がコンパイルされ、sp_executesqlを呼び出したバッチの実行プランとは別の実行プランとして実行されます。

したがって、コマンドテキストのプランが既にプランキャッシュにある場合でも、実行されるたびにsp_executesql それ自体がコンパイルされます。@PaulWhiteは彼の回答で、sp_executesqlへのほとんどの呼び出しが実際にはキャッシュされていないことを示しています

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.