Nous exécutons une installation de Dynamics AX 2012 avec SQL Server 2012. Je sais que les curseurs ne doivent plus être utilisés, mais la hache l'utilise et nous ne pouvons pas changer ce comportement afin que nous puissions travailler avec elle.
Aujourd'hui, j'ai attrapé une très mauvaise requête avec plus de 53 millions de lectures et une durée d'exécution supérieure à 20 minutes.
J'ai attrapé cette requête via notre outil de surveillance Sentryone.
declare @p1 int
set @p1=1073773227
declare @p2 int
set @p2=180158805
declare @p5 int
set @p5=16
declare @p6 int
set @p6=1
declare @p7 int
set @p7=2
exec sp_cursorprepexec @p1 output,@p2 output,N'@P1 bigint,@P2 nvarchar(5),@P3 bigint,@P4 nvarchar(8),@P5 bigint,@P6 bigint,@P7 bigint,@P8 bigint,@P9 bigint,@P10 bigint,@P11 bigint,@P12 bigint,@P13 bigint,@P14 bigint,@P15 bigint,@P16 bigint,@P17 bigint,@P18 bigint,@P19 nvarchar(5),@P20 bigint,@P21 bigint,@P22 bigint,@P23 bigint,@P24 bigint',N'SELECT T1.PRODUCT,T1.EXTERNALVENDPARTY,T1.LIFECYCLESTATUS,T1.RECID,T2.ECORESPRODUCT,T2.ECORESDISTINCTPRODUCTVARIANT,T2.SGE,T2.ECORESREFORDERNUM,T2.ORDERNUM,T2.RECID,T3.ECORESREFORDERNUM,T3.NAME1,T3.NAME2,T3.NAME3,T3.RECID,T4.ECORESPRODUCT,T4.EXTERNALITEMID,T4.ECORESDISTINCTPRODUCTVARIANT,T4.RECID,T5.RECID,T5.PERSON,T6.RECID,T6.NAME,T6.INSTANCERELATIONTYPE,T7.RECID,T7.NAME,T7.INSTANCERELATIONTYPE,T8.PARTY,T8.ACCOUNTNUM,T8.RECID,T9.RECID,T9.DISPLAYPRODUCTNUMBER,T9.INSTANCERELATIONTYPE,T10.PRODUCT,T10.CATEGORY,T10.RECID,T11.RECID,T11.CODE,T11.NAME,T11.INSTANCERELATIONTYPE FROM INVENTTABLE T1 CROSS JOIN ECORESPRODUCTORDERNUM T2 CROSS JOIN ECORESPRODUCTORDERNUMTRANSLATION T3 LEFT OUTER JOIN VENDEXTERNALITEM T4 ON ((T4.PARTITION=5637144576) AND ((T2.ECORESPRODUCT=T4.ECORESPRODUCT) AND (T4.ECORESDISTINCTPRODUCTVARIANT=@P1))) CROSS JOIN HCMWORKER T5 CROSS JOIN DIRPARTYTABLE T6 CROSS JOIN DIRPARTYTABLE T7 CROSS JOIN VENDTABLE T8 CROSS JOIN ECORESPRODUCT T9 CROSS JOIN ECORESPRODUCTCATEGORY T10 CROSS JOIN ECORESCATEGORY T11 WHERE (((T1.PARTITION=5637144576) AND (T1.DATAAREAID=N''087'')) AND (T1.DATAAREAID=@P2)) AND ((T2.PARTITION=5637144576) AND ((T2.ECORESPRODUCT=T1.PRODUCT) AND (T2.SGE=@P3))) AND ((T3.PARTITION=5637144576) AND ((T3.ECORESREFORDERNUM=T2.ECORESREFORDERNUM) AND (T3.LANGUAGEID=@P4))) AND ((T5.PARTITION=5637144576) AND (T5.RECID=T2.PRODUCTMANAGER)) AND (((T6.PARTITION=5637144576) AND (T6.INSTANCERELATIONTYPE IN (@P5,@P6,@P7,@P8,@P9,@P10,@P11) )) AND (T6.RECID=T5.PERSON)) AND (((T7.PARTITION=5637144576) AND (T7.INSTANCERELATIONTYPE IN (@P12,@P13,@P14,@P15,@P16,@P17,@P18) )) AND (T1.EXTERNALVENDPARTY=T7.RECID)) AND (((T8.PARTITION=5637144576) AND (T8.DATAAREAID=N''087'')) AND ((T7.RECID=T8.PARTY) AND (T8.DATAAREAID=@P19))) AND (((T9.PARTITION=5637144576) AND (T9.INSTANCERELATIONTYPE IN (@P20,@P21,@P22) )) AND (T9.RECID=T1.PRODUCT)) AND ((T10.PARTITION=5637144576) AND (T10.PRODUCT=T9.RECID)) AND (((T11.PARTITION=5637144576) AND (T11.INSTANCERELATIONTYPE IN (@P23,@P24) )) AND (T11.RECID=T10.CATEGORY))',@p5 output,@p6 output,@p7 output,0,N'087',5637146082,N'de',41,2303,2377,2975,2978,5329,6886,41,2303,2377,2975,2978,5329,6886,N'087',3265,3266,3267,2665,4423
select @p1, @p2, @p5, @p6, @p7
La première chose que j'ai remarquée est que cette requête utilisait un curseur. En dehors de la curiosité, j'ai copié la déclaration et l'a exécutée en studio de gestion sans le curseur (je dois admettre que j'ai remplacé les paramètres de la requête afin que je puisse l'exécuter). Dans SSMS, la requête est terminée en 30 secondes. Pas très vite, mais toujours plus rapide que l'alternative du curseur.
Ici, je vous fournis les deux plans:
Le plan sans le curseur est toujours un très mauvais plan, mais c'est beaucoup mieux. Ma question ici est: Quelqu'un peut-il vous expliquer pourquoi la version du curseur a besoin de 53 millions de lectures?
Statistiques pour la requête avec curseur:
Duration CPU Reads Writes Est Rows Actual Rows
1.396.212 1.379.157 53.270.895 3.878 30 2
Statistiques pour la requête sans curseur:
Duration CPU Reads Writes Est Rows Actual Rows
23.337 1.703 665.113 13 4.287 34.813
Il semble étrange d'obtenir 34 813 rangées au lieu de 2; Mais je suis sûr que je me suis rempli dans les bons paramètres. Je pensais que c'était peut-être un ennemi drôle de SQL Sentry depuis que je viens de copier les statistiques de là.
J'espère que je pourrais vous fournir toutes les informations nécessaires pour vous. En outre, si quelqu'un a de bonnes lectures, mieux comprendre les curseurs qui seraient formidables.
Tout d'abord, cela me surprend que le nombre réel de lignes pour les deux requêtes de SQL Sentry n'est pas plus ou moins la même chose.
Seconde. Il est difficile de dire comment corriger vos estimations figurent dans le plan avec un curseur sans plan réel, mais certaines choses me démarquent. (P.s.: Reportez-vous à - ma réponse ici pour obtenir un plan réel).
Cela étant dit, il convient de noter quelques choses à partir de votre plan estimé.
Il y a un avertissement sur index inégalés en raison de la paramétrisation. Supprimer le paramétrage SO SQL Server peut utiliser les personnes inégalées pourraient considérablement améliorer les E/S.
Le nombre estimé de lignes entre les 2 plans est de manière spectaculaire. Dans votre plan avec un curseur, vous avez un nombre estimé de lignes de Vendexternalitem de 11. Dans votre plan sans curseur, vous avez un nombre estimé et réel de rangées de près de 200k. Si vos documents de 200K se rendent réellement dans cet opérateur de bobine qui pourrait être douloureux.
Tous les opérateurs ont des estimations extrêmement différentes (beaucoup plus petites dans le plan avec un curseur), alors votre plan a peut-être été compilé et mis en cache avec différentes valeurs de paramètre que vous utilisez dans la requête sans curseur. (connu sous le nom paramètre reniflant )
Il existe également un choix très étrange dans la recherche de la clé d'index + sur la table Invent. Le plan utilise Typeidx, puis effectue des recherches clés de l'index en cluster (itemIDX). Si vos estimations sont éteintes et que SQL Server doit effectuer beaucoup de recherches clés qui pourraient expliquer beaucoup de IO aussi. Je ne connais pas le stopidx que vous avez dans votre plan sans curseur mais il semble que cela couvre donc c'est probablement un meilleur choix pour les paramètres que vous avez fournis. Je suppose que cela a choisi le typeidx car il est beaucoup plus étroit, mais cela pourrait être dû à différentes valeurs de temps compilées que vous fournissez l'exécution problématique.
En bref, je supprimerais la paramétrisation de cette requête dans la hache afin qu'elle génère un plan avec les valeurs réelles afin de choisir les "meilleurs" index comme en témoigne le plan (et les temps d'exécution) dans SSMS. Cela permettrait également à SQL Server d'utiliser les index filtrés. Pour ce faire, avoir un développeur ajoutez le bouton forceliterals
keyword Dans le code de l'application où cette requête est exécutée et voir ce qui se passe.
Cela vous laisserait probablement toujours avec une requête qui prend 30 secondes (semblable à ce que vous avez dans les SSMS), mais c'est juste une question de réglage. Il y a des avertissements indexés manquant dans vos plans et je pense qu'un indice sur EcoreProDuctOnerNum.sge pourrait aider par exemple, mais je ne connais pas ces tables et je pense qu'ils sont ajoutés par les personnalisations. Les principes généraux de syntonisation aideraient ici mais ce serait probablement trop large pour cette réponse (couvert index, ...)