web-dev-qa-db-fra.com

Pourquoi cela optimise-t-il pour une inconnue améliore ma requête de plusieurs secondes?

OK, j'ai donc une requête de procédure non stockée que nous utilisons dans un rapport SSRS. Cette requête a été Hellishly lent (j'ai eu le original Version de cette requête en cours d'exécution depuis deux heures, toujours pas terminée), dans un effort Pour l'améliorer, je l'ai réécrit à partir de zéro et je suis proposé ce qui suit:

Maintenant, voici la partie de problème de mot ennuyeux:

Nous voulons tirer une liste des clients TOP 5 par représentant des ventes, mais exclure le TOP 10 Total des clients de cette liste. (Donc, si John Doe a des clients A, B, C, D et E et CLIENT C est l'un des 10 meilleurs, puis tirer uniquement A, B, D et E.)

Pour ce faire, la première requête a utilisé une IN (... NOT IN ( ) ), alors je pensais que la nidification de IN était la question, de réécrire, je faisais un OUTER APPLY qui a vraiment cassé tout.

Quoi qu'il en soit, j'ai réparé tout cela et j'ai dirigé la requête, et il a toujours pris 10-15 secondes que j'ai supposé que le paramètre renifle. Pour étudier, j'ai dirigé la requête dans SSMS, a ajouté OPTION (RECOMPILE) (pour voir quel plan de requête générerait) et obtenu ce qui suit:

Plan 1

Il peut être visualisé ici sur la "pâte" de Brent Ozar . La requête qui a généré c'était:

DECLARE @Top10Temp TABLE (Id INT)
INSERT INTO @Top10Temp
SELECT TOP 10 Id
FROM Object1
WHERE Column2  = @ReportId
  AND Column3  = 0
GROUP BY Id
ORDER BY SUM(Column4 + Column5) DESC

SELECT Object2.*
FROM Object1 AS Object2
OUTER APPLY (
    SELECT TOP 5
        Object3.Id,
        SUM(Object3.Column4 + Object3.Column5) AS Column6
    FROM Object1 AS Object3
    WHERE Object3.Column3 = 0
      AND Object3.Column7 = Object2.Column7
      AND Object3.Column2 = @ReportId
    GROUP BY
        Object3.Id
    ORDER BY
        SUM(Object3.Column4 + Object3.Column5) DESC
) AS Object4
WHERE Object2.Column2 =      @ReportId
  AND Object2.Column3 =      0
  AND Object2.Id      =      Object4.Id
  AND Object2.Id      NOT IN (SELECT Id FROM @Top10Temp)
ORDER BY Object2.Column7
OPTION (RECOMPILE)

Maintenant, le même requête Mais avec OPTION (OPTIMIZE FOR UNKNOWN) généré le plan suivant:

Plan 2

qui peut également être visualisé pour "coller le plan" . Ce plan a été exécuté dans moins de 1 seconde.

Si j'ajoute OPTION (OPTIMIZE FOR (@ReportId = #)), où # est le même comme la variable @ReportId, je reçois le même plan de requête que la seconde.

Est-ce que j'ai fait quelque chose de mal? J'ai du mal à comprendre ce qui s'est passé, de sorte que toute information est très appréciée. (Je n'aime pas vraiment vraiment essayer d'influencer l'optimiseur via des allumettes, mais si c'est nécessaire, je le garderai.)

7
Der Kommissar

"Pour enquêter, j'ai dirigé la requête dans SSMS ..." C'est la question. Les variables locales utilisent le vecteur de densité des statistiques donnant une estimation beaucoup meilleure rangée et optimisent donc déjà pour inconnu. SQL dynamique paramétré utilise l'histogramme, qui tire le nombre total de lignes pour une section donnée.

Regardez le nombre estimé VS Nombre réel de lignes pour chacune de vos colliers les liens de plan. Le deuxième lien a une meilleure estimation de Waaaayyyyy que le premier.

Je déploierais votre requête SSRS sur une instance de développement et exécutez des tests comme je soupçonne que vous pourriez avoir des problèmes de performance.

BTW, mettez à jour des statistiques ou des index de reconstruction sur ces tables de bête, si vous le pouvez.

Liens: à l'intérieur du vecteur d'histogramme et de densité de statistiques

5
thundercougarfalcon

Le plan lent a une mauvaise estimation de la cardinalité qui sort de l'indice de recherche au nœud 4. Le nombre estimé de lignes est de 1, mais le nombre réel de lignes est de 3261. Voici le prédicat de recherche:

Seek Keys[1]: Prefix: Database1.Schema1.Object1.Column2, Database1.Schema1.Object1.Column3 = Scalar Operator(ScalarString7), Scalar Operator(ScalarString2)

Vous filtrez sur deux colonnes différentes de la même table. Souvent SQL Server n'a pas suffisamment d'informations pour donner une estimation précise de ce scénario. Il permet de modéliser des hypothèses de modélisation qui dépendent de votre version CE, des correctifs, des drapeaux de trace, etc. Par exemple, il peut supposer que les colonnes n'ont aucune corrélation et multiplient les sélectivités ensemble. Cela peut entraîner une estimation faible s'il y a une certaine corrélation entre les filtres.

En général, je dirais que si vous obtenez de bonnes performances avec une mauvaise estimation, vous avez probablement eu de la chance et votre chance peut s'épuiser à un moment donné. J'essaierais de résoudre cette estimation. Je ne peux pas vous donner des instructions précises car il y a trop d'informations manquantes (vous ne pourrez pas partager certaines des informations manquantes dues aux problèmes de propriété intellectuelle), mais je peux dire qu'une statistique ou un index de plusieurs colonnes pourrait aider. Stocker les clés primaires de la table après le filtrage dans une table TEMP est une méthode qui devrait toujours fonctionner. Avec une estimation plus précise, je m'attendrais à voir un plan de requête similaire au plan rapide.

Vous n'avez rien fait de mal en ajoutant le OPTION (RECOMPILE) indice. Vous avez peut-être obtenu une mauvaise performance simplement par la malchance. L'optimisation de l'intégration des paramètres aide généralement au lieu de causer des problèmes. OPTIMIZE FOR UNKNOWN Le SQL Server utilise les objets de statistiques différemment et il arrive simplement que vous ayez une estimation plus près de la réalité lorsque vous l'utilisez.

Je n'utiliserais pas OPTIMIZE FOR UNKNOWN En tant que solution à long terme. Le plan de requête ne change pas en fonction de la valeur de @ReportId, Ce qui pourrait causer des problèmes lorsque vous modifiez la valeur de la variable. C'est aussi un peu indirect et vous avez admis que vous ne comprenez pas comment cela fonctionne. Il serait préférable d'attaquer le problème plus directement au problème en fixant l'estimation de la cardinalité ou en matérialisant de manière stratégique les résultats intermédiaires dans des tables temporaires. En règle générale, vous devriez éviter d'utiliser des variables de table car ils n'ont pas de statistiques. Les variables de table ont des cas d'utilisation très limité et ma recommandation pour vous est de les utiliser que lorsque vous n'avez pas d'autre choix.

4
Joe Obbish