web-dev-qa-db-fra.com

Pourquoi les temps de compilation élevés pour les fonctions avec XML sur SQL Server 2016?

Nous avons des fonctions en ligne qui recueillissent des données dans XML, passent de la liste XML dérivée à d'autres fonctions, qui la déchiquetent ensuite et la réformer comme une chaîne.

(Votre "tu ne devrais pas faire ce genre de choses dans T-SQL" est une discussion pour un autre jour.)

Cela a bien fonctionné pendant des années avec 2005 et 2008 R2. Nous passons maintenant à 2016 SP1. Une requête utilisant ces fonctions qui fonctionne sous une seconde sur notre serveur de production fonctionne maintenant encore plus rapidement en 2016 SP1. Ce serait génial, mais il faut une heure pour compiler sur 2016 SP1. Sérieusement:

enter image description here

Environnement de production:

enter image description here

(Ce sont tous deux de SQL Sentry Plan Explorer.)

Nous avons essayé avec la base de données en 2008 (130) et 2016 (130) les niveaux de compatibilité (mais nous n'avons pas encore joué avec l'estimation de la cardinalité héritée et les paramètres "Optimizer de requête", qui sont à la fois "Off" actuellement). Nous avons essayé d'utiliser QUERYTRACEON 9481, qui semble avoir aucun effet.

Encore une fois, cela ne concerne pas les plans résultants, car ils fonctionnent tous dans une belle quantité de temps. Il s'agit du temps nécessaire pour faire le plan.

Nous avons réussi à reproduire ce problème-- dans une étendue ... avec un ensemble de code simplifié. Une déclaration appelant la fonction de niveau supérieur de l'exemple ci-dessous prend 30 à 60 secondes pour compiler sur SQL Server 2016 (SP1-CU5), mais il faut moins d'une seconde pour compiler et exécuter sur SQL Server 2008 R2 (SP3).

Exemple

/*

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

Production @@version Où la compilation n'est pas un problème:

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

Test @@version Où la compilation prend "pour toujours":

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

Des questions

  1. Pourquoi une telle dégradation est-elle une telle dégradation jusqu'en 2016 à partir de 2008 R2?

  2. Cette réponse révèle-t-elle une solution facile à ce dilemme de changer la façon dont tout cela fonctionne? (J'espère que des drapeaux de trace magiques ou une prochaine mise à jour Microsoft.)

(Si c'était SQL Server 2017, j'utiliserais JSON pour recueillir et transmettre les données, ce qui semble être plus rapide et plus bas sur le dessus, puis j'utiliserais les fonctions JSON pour déchiqueter et STRING_AGG Pour réformer le texte. Mais hélas, ce n'est pas encore disponible.)

Basé sur une pointe de Joe'bshbish , j'ai rassemblé les résultats lors de l'utilisation Drapeau de trace 8675 , en utilisant ce code:

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

Lors d'une instance R2 de 2008, il a fallu beaucoup moins d'une seconde et produit ceci:

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.

Sur une instance SP1-CU5 de 2016, il a fallu 1 minute 11 secondes et produisit ceci:

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.

Bien que plus se passe, on dirait que le temps écoulé était que 0,018 (secondes?), Qui est inférieure à 0,03 sur 2008 R2. Donc, cette phase de compilation/optimisation/exécution ne doit pas être ce qui prend si longtemps. Mais quelque chose est définitivement.


Notre instance de production 2016 éventuelle aura le même "matériel" comme production actuelle 2008 R2. Test/Dev 2016 Les instances ont des spécifications différentes, de sorte que ces comparaisons ne sont pas des pommes aux pommes. Prod est 78 gig. Dev est de 16 gig. Mais j'ai testé sur une autre boîte R2 2008 avec 20 gig et c'était rapide. En outre, nous parlons de petites quantités de données bien indexées. Et pendant la compilation Little IO mais beaucoup de CPU.

Je pouvais voir que les statistiques étant un problème avec les fonctions réelles qui ont frappé de vrais tables (grandes), mais dans mon exemple de manière intelligente, il faut 1+ minute pour effectuer une simple manipulation de XML/texte simple sur 5 rangées entière chargées. Je pourrais taper les résultats plus rapidement. :) Je pense que nous avons des statistiques automatiques de la production et je ne vois aucune autre question de performance apparemment liée aux statistiques. Et une autre non-production de 2008 R2 Dev/Essai d'environnement (avec une copie de production obsolète similaire en 2016 Dev/Test) est cassée.

7
Riley Major

Il est possible que CU7 pour SQL Server 2016 SP1 résout votre problème. L'article de KB pertinent semble être KB 4056955 - Correction: les requêtes qui distribuent des données de chaîne ou binaires à XML prennent beaucoup de temps à compiler dans SQL Server 2016 .

J'ai dirigé votre code de reproduction dans SQL Server 2016 SP1 CU6 et a reçu ce qui suit pour les heures de compilation par SET STATISTICS TIME ON:

SQL Server Analysez et compilez l'heure: CPU Time = 24437 ms, temps écoulé = 24600 ms.

SQL Server Analysez et compilez l'heure: CPU Time = 48968 ms, temps écoulé = 49451 ms.

Voici comment la compilation a changé après la mise à niveau vers SP1 CU7:

SQL Server Analysez et compilez l'heure: Time CPU = 16 ms, heure écoulée = 16 ms.

SQL Server Analysez et compilez l'heure: Time CPU = 16 ms, temps écoulé = 17 ms.

SQL Server Analysez et compilez l'heure: Time CPU = 16 ms, temps écoulé = 17 ms.

SQL Server Analysez et compilez l'heure: CPU Time = 44 ms, temps écoulé = 44 ms.

C'est environ 750 fois plus vite.

7
Joe Obbish

Je ne peux pas répondre pourquoi 2016 prend beaucoup plus de temps, mais une solution de contournement semble être d'obscurcir certaines des opérations de l'optimiseur. Si vous masquez la partie de construction XML du processus en le plaçant derrière une fonction scalaire, le temps de compilation tombe sur environ une seconde. Il est toujours considérablement plus lent qu'en 2008 R2, mais cela pourrait être assez rapide.

/*

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