web-dev-qa-db-ja.com

SQL Server 2016でXMLを使用する関数のコンパイル時間が長いのはなぜですか?

データをXMLに収集し、その派生XMLを他の関数に渡し、それを細断して文字列として再構成するインライン関数があります。

(「T-SQLでそのようなことをしてはいけない」は別の日の議論です。)

これは、2005年と2008年のR2で長年うまく機能していました。現在、2016 SP1にアップグレードしています。本番サーバーで1秒未満で実行されるこれらの関数を使用したクエリは、2016 SP1ではさらに高速に実行されます。それは素晴らしいことですが、2016 SP1でコンパイルするには1時間かかります。真剣に:

enter image description here

本番環境:

enter image description here

(どちらもSQL Sentry Plan Explorerからのものです。)

2008年(100)と2016年(130)の互換性レベルでデータベースを試しました(ただし、「レガシーカーディナリティ推定」と「クエリオプティマイザーの修正」の設定はまだ使用していません。 QUERYTRACEON 9481を使用してみましたが、効果がないようです。

繰り返しになりますが、これらはすべて適切な時間で実行されるため、結果の計画に関するものではありません。計画を立てるのにかかる時間です。

この問題は、コードの簡略化されたセットである程度再現できました。以下の例のトップレベル関数を呼び出すステートメントは、SQL Server 2016(SP1-CU5)でコンパイルするのに30〜60秒かかりますが、SQL Server 2008 R2(SP3)でコンパイルして実行するのに1秒もかかりません。

/*

Create and populate table...

*/

CREATE TABLE TestXMLStuff (OrderID int, ProdLength int, ProdWidth int, ProdHeight int);
INSERT INTO TestXMLStuff (OrderID, ProdLength, ProdWidth, ProdHeight) VALUES
    (1, 10, 15, 20),
    (1, 15, 20, 25),
    (2, 20, 25, 30),
    (2, 25, 30, 35),
    (2, 30, 35, 40);
GO

/*

Function which accepts XML, shreds it and reforms it as a string...

*/


CREATE FUNCTION TestCalc
(   
    @T varchar(8000),
    @X xml
)
RETURNS TABLE 
AS
RETURN 
    WITH p AS
    (
        SELECT  
            LF = CHAR(13) + CHAR(10),
            Tab = CHAR(9),
            T = isNull(@T,'')
    ), pid AS
    (
        SELECT
            isNull(ProdInfoXMLTable.ProdInfoXML.query('(/ProdInfo)').value('(.)[1]','varchar(max)'),'') AS ProdInfoText
        FROM        (
                        SELECT
                            ProdInfoXML =
                                (
                                    SELECT
                                        ProdInfo = 
                                            CASE WHEN Products.ProdNum > 1 THEN '--' + p.LF ELSE '' END +
                                            'Product Number: ' + CONVERT(varchar(50),Products.ProdNum) + p.LF +
                                                CASE WHEN Products.ProdLength       = '' THEN '' ELSE p.Tab + 'Length: '                + Products.ProdLength       + p.LF END +
                                                CASE WHEN Products.ProdWidth        = '' THEN '' ELSE p.Tab + 'Width: '                 + Products.ProdHeight       + p.LF END +
                                                CASE WHEN Products.ProdHeight       = '' THEN '' ELSE p.Tab + 'Height: '                + Products.ProdHeight       + p.LF END
                                    FROM        (
                                                    SELECT
                                                        ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS ProdNum,
                                                        isNull(P.X.value('(./Length)[1]','varchar(500)'),'') AS ProdLength,
                                                        isNull(P.X.value('(./Width)[1]','varchar(500)'),'') AS ProdWidth,
                                                        isNull(P.X.value('(./Height)[1]','varchar(500)'),'') AS ProdHeight
                                                    FROM        @x.nodes('/Products/Product') AS P(X)
                                                ) AS Products
                                    CROSS JOIN  p
                                    FOR XML PATH(''), TYPE
                                )
                    ) AS ProdInfoXMLTable
    )
    SELECT
        Final = p.T + p.LF + p.LF + pid.ProdInfoText
    FROM        p
    CROSS JOIN  pid;

GO

/*

Function to create XML in the format required for TestCalc...

*/

CREATE FUNCTION TestGetXML
(   
    @N int
)
RETURNS TABLE 
AS
RETURN 
    WITH p AS
    (
        SELECT  
            N = isNull(@N,0)
    )
    SELECT
        ProdInfoXML =
            (
                SELECT
                    [Length] = ProdData.ProdLength,
                    [Width] = ProdData.ProdWidth,
                    [Height] = ProdData.ProdHeight
                FROM        TestXMLStuff ProdData
                WHERE       ProdData.OrderID = @N
                FOR XML PATH('Product'), ROOT('Products'), TYPE
            );
GO

/*

Function to join the other two functions, gathering the XML and feeding it to the string creator which shreds and reforms it...

*/

CREATE FUNCTION TestGetFromTableUsingFunc
(   
    @N int
)
RETURNS TABLE 
AS
RETURN 
    WITH p AS
    (
        SELECT  
            N = isNull(@N,0)
    )
    SELECT
        FinalResult = 'This is a ' + TestCalcResults.Final
    FROM        p
    CROSS APPLY TestGetXML
                (
                    p.N
                ) AS x
    CROSS APPLY TestCalc
                (
                    'test',
                    x.ProdInfoXML
                ) AS TestCalcResults;
GO

/*

Code to call the function. This is what takes around 60 seconds to compile on our 2016 system but basically no time on the 2008 R2 system.

*/

SELECT      *
FROM        TestXMLStuff
CROSS APPLY TestGetFromTableUsingFunc
            (
                OrderID
            )
OPTION      (RECOMPILE);
GO

プロダクション@@versionコンパイルが問題にならない場合:

Microsoft SQL Server 2008 R2 (SP3) - 10.50.6000.34 (X64)

@@versionをテストします。コンパイルには「永久に」かかります。

Microsoft SQL Server 2016 (SP1-CU5) (KB4040714) - 13.0.4451.0 (X64) 

ご質問

  1. なぜ2008 R2から2016年にかけてコンパイル時間にこのような低下があるのですか?

  2. その答えは、このすべてが機能する方法を変更することなく、このジレンマの簡単な解決策を明らかにしますか? (私はマジックトレースフラグまたは次のMicrosoftアップデートを期待しています。)

(これがSQL Server 2017である場合、私はJSONを使用してデータを収集して渡すため、より高速でオーバーヘッドが少ないようです。次に、JSON関数を使用して細断処理し、STRING_AGGを使用してテキストに再構成します。しかし、悲しいかな、それはまだ利用できません。)

Joe Obbish からのヒントに基づいて、このコードを使用して トレースフラグ8675 を使用したときに結果を収集しました。

DBCC TRACEON(3604)
SELECT      *
FROM        TestXMLStuff
CROSS APPLY TestGetFromTableUsingFunc
            (
                OrderID
            )
OPTION      (RECOMPILE, QUERYTRACEON 8675);
DBCC TRACEOFF(3604)

2008 R2インスタンスでは、1秒もかかりませんでした。

DBCC execution completed. If DBCC printed error messages, contact your system administrator. End of simplification, time: 0.008 net: 0.008 total: 0.008 net: 0.008

end exploration, tasks: 597 no total cost time: 0.005 net: 0.005 total: 0.014 net: 0.014

end search(0),  cost: 2071.66 tasks: 2267 time: 0.005 net: 0.005 total: 0.02 net: 0.02

end exploration, tasks: 2703 Cost = 2071.66 time: 0.002 net: 0.002 total: 0.022 net: 0.022

end search(1),  cost: 1731.11 tasks: 3362 time: 0.004 net: 0.004 total: 0.026 net: 0.026

end exploration, tasks: 3363 Cost = 1731.11 time: 0 net: 0 total:
0.026 net: 0.026

end search(1),  cost: 1731.11 tasks: 3382 time: 0 net: 0 total: 0.026 net: 0.026

end exploration, tasks: 3413 Cost = 1731.11 time: 0 net: 0 total:
0.027 net: 0.027

end search(2),  cost: 1731.11 tasks: 3515 time: 0 net: 0 total: 0.027 net: 0.027

End of post optimization rewrite, time: 0.001 net: 0.001 total: 0.029 net: 0.029

End of query plan compilation, time: 0.001 net: 0.001 total: 0.03 net:
0.03


(5 row(s) affected) DBCC execution completed. If DBCC printed error messages, contact your system administrator.

2016 SP1-CU5インスタンスでは、1分11秒かかり、次のようになりました。

DBCC execution completed. If DBCC printed error messages, contact your system administrator. End of simplification, time: 0.004 net: 0.004 total: 0.004 net: 0.004

end exploration, tasks: 612 no total cost time: 0.003 net: 0.003 total: 0.008 net: 0.008

end exploration, tasks: 613 no total cost time: 0 net: 0 total: 0.008 net: 0.008

end exploration, tasks: 2305 no total cost time: 0.002 net: 0.002 total: 0.011 net: 0.011

end exploration, tasks: 2306 no total cost time: 0 net: 0 total: 0.011 net: 0.011

end search(0),  cost: 4402.32 tasks: 2306 time: 0 net: 0 total: 0.011 net: 0.011

end exploration, tasks: 2738 Cost = 4402.32 time: 0.001 net: 0.001 total: 0.013 net: 0.013

end exploration, tasks: 2739 Cost = 4402.32 time: 0 net: 0 total:
0.013 net: 0.013

end exploration, tasks: 3466 Cost = 4402.32 time: 0.002 net: 0.002 total: 0.015 net: 0.015

end exploration, tasks: 3467 Cost = 4402.32 time: 0 net: 0 total:
0.015 net: 0.015

end search(1),  cost: 3938.19 tasks: 3467 time: 0 net: 0 total: 0.015 net: 0.015

end exploration, tasks: 3468 Cost = 3938.19 time: 0 net: 0 total:
0.015 net: 0.015

end exploration, tasks: 3469 Cost = 3938.19 time: 0 net: 0 total:
0.015 net: 0.015

end exploration, tasks: 3489 Cost = 3938.19 time: 0 net: 0 total:
0.015 net: 0.015

end exploration, tasks: 3490 Cost = 3938.19 time: 0 net: 0 total:
0.015 net: 0.015

end search(1),  cost: 3938.19 tasks: 3490 time: 0 net: 0 total: 0.015 net: 0.015

end exploration, tasks: 3521 Cost = 3938.19 time: 0 net: 0 total:
0.015 net: 0.015

end exploration, tasks: 3522 Cost = 3938.19 time: 0 net: 0 total:
0.015 net: 0.015

end exploration, tasks: 3625 Cost = 3938.19 time: 0 net: 0 total:
0.016 net: 0.016

end exploration, tasks: 3626 Cost = 3938.19 time: 0 net: 0 total:
0.016 net: 0.016

end search(2),  cost: 3938.19 tasks: 3626 time: 0 net: 0 total: 0.016 net: 0.016

End of post optimization rewrite, time: 0 net: 0 total: 0.016 net:
0.016

End of query plan compilation, time: 0.001 net: 0.001 total: 0.018 net: 0.018


(5 row(s) affected) DBCC execution completed. If DBCC printed error messages, contact your system administrator.

さらに進んでいますが、経過時間はたったの0.018(秒?)で、2008 R2の0.03よりも短いようです。したがって、コンパイル/最適化/実行のこのフェーズは、それほど時間がかかってはいけません。しかし、間違いなく何かがあります。


最終的な2016年のプロダクションインスタンスには、現在のプロダクション2008 R2と同じ「ハードウェア」があります。 Test/Dev 2016インスタンスは仕様が異なるため、これらの比較は単なる比較ではありません。製品は78ギグです。 Devは16ギガです。しかし、20ギガの別の2008 R2ボックスでテストしたところ、高速でした。また、適切に索引付けされた少量のデータについても話します。そしてコンパイル中は少しIOですが大量のCPUが必要です。

統計が実際の(大きな)テーブルにヒットする実際の関数の問題であることがわかりましたが、私の考案された単純化された例では、単純なXML /テキスト操作に1分以上かかります5整数の多い行。結果をより速く入力できました。 :)私はプロダクションに関する自動統計を持っていると信じています、そして統計に関連しているように見える他のパフォーマンス問題は他にありません。また、別の非プロダクション2008 R2開発/テスト環境(2016開発/テストと同様に古いプロダクションコピーがある)は、不安定な分割です。

7
Riley Major

SQL Server 2016 SP1のCU7 で問題が解決する可能性があります。関連するKB記事は次のようです KB 4056955-修正:文字列またはバイナリデータをXMLにキャストするクエリは、SQL Server 2016でコンパイルするのに長い時間がかかります

SQL Server 2016 SP1 CU6で再現コードを実行し、SET STATISTICS TIME ONごとのコンパイル時間について以下を取得しました。

SQL Serverの解析およびコンパイル時間:CPU時間= 24437 ms、経過時間= 24600 ms。

SQL Serverの解析およびコンパイル時間:CPU時間= 48968ミリ秒、経過時間= 49451ミリ秒。

以下は、SP1 CU7にアップグレードした後のコンパイル時間の変化です。

SQL Serverの解析およびコンパイル時間:CPU時間= 16ミリ秒、経過時間= 16ミリ秒。

SQL Serverの解析およびコンパイル時間:CPU時間= 16ミリ秒、経過時間= 17ミリ秒。

SQL Serverの解析およびコンパイル時間:CPU時間= 16ミリ秒、経過時間= 17ミリ秒。

SQL Serverの解析およびコンパイル時間:CPU時間= 44 ms、経過時間= 44 ms。

約750倍高速です。

7
Joe Obbish

2016年にこれほど時間がかかる理由をお答えすることはできませんが、回避策として、オプティマイザからの操作の一部を不明瞭にすることが考えられます。プロセスのXML構築部分をスカラー関数の背後に配置してマスクすると、コンパイル時間は約1秒に短縮されます。 2008 R2よりもかなり低速ですが、十分に高速かもしれません。

/*

Add this scalar function as an opaque wrapper around the (transparent) inline XML gathering function...

*/

CREATE FUNCTION TestGetXMLScalar
(
    @N int
)
RETURNS xml
AS
BEGIN
    RETURN
    (
        SELECT
            ProdInfoXML
        FROM        TestGetXML
                    (   
                            @N
                    )
    );

END
GO

/*

Then, use the scalar function when orchestrating everything...

*/

CREATE FUNCTION TestGetFromTableUsingFuncScalar
(   
    @N int
)
RETURNS TABLE 
AS
RETURN 
    WITH p AS
    (
        SELECT  
            N = isNull(@N,0)
    )
    SELECT
        FinalResult = 'This is a ' + TestCalcResults.Final
    FROM        p
    CROSS APPLY TestCalc
                (
                    'test',
                    dbo.TestGetXMLScalar(@N)
                ) AS TestCalcResults;
GO

/*

Finally, try out the new function...

*/

SELECT      *
FROM        TestXMLStuff
CROSS APPLY TestGetFromTableUsingFuncScalar
            (
                OrderID
            )
OPTION      (RECOMPILE);
GO
0
Riley Major