OPTION (RECOMPILE) est toujours plus rapide; Pourquoi?

169

J'ai rencontré une situation étrange où l'ajout OPTION (RECOMPILE)à ma requête entraîne son exécution en une demi-seconde, tandis que son omission entraîne la durée de la requête bien plus de cinq minutes.

C'est le cas lorsque la requête est exécutée depuis l'Analyseur de requêtes ou depuis mon programme C # via SqlCommand.ExecuteReader(). Appeler (ou ne pas appeler) DBCC FREEPROCCACHEou DBCC dropcleanbuffersne fait aucune différence; Les résultats de la requête sont toujours renvoyés instantanément avec OPTION (RECOMPILE)et plus de cinq minutes sans. La requête est toujours appelée avec les mêmes paramètres [pour le bien de ce test].

J'utilise SQL Server 2008.

Je suis assez à l'aise avec l'écriture de SQL, mais je n'ai jamais utilisé de OPTIONcommande dans une requête auparavant et je ne connaissais pas tout le concept des caches de plan avant de scanner les messages sur ce forum. D'après ce que je comprends des messages, c'est OPTION (RECOMPILE)une opération coûteuse. Cela crée apparemment une nouvelle stratégie de recherche pour la requête. Alors pourquoi est-ce que les requêtes suivantes qui omettent le OPTION (RECOMPILE)sont si lentes? Les requêtes suivantes ne devraient-elles pas utiliser la stratégie de recherche qui a été calculée lors de l'appel précédent qui incluait l'indication de recompilation?

Est-il très inhabituel d'avoir une requête qui nécessite un indice de recompilation à chaque appel?

Désolé pour la question d'entrée de gamme, mais je ne peux pas vraiment en faire la tête ou la queue.

MISE À JOUR: on m'a demandé de publier la requête ...

select acctNo,min(date) earliestDate 
from( 
    select acctNo,tradeDate as date 
    from datafeed_trans 
    where feedid=@feedID and feedDate=@feedDate 

    union 

    select acctNo,feedDate as date 
    from datafeed_money 
    where feedid=@feedID and feedDate=@feedDate 

    union 

    select acctNo,feedDate as date 
    from datafeed_jnl 
    where feedid=@feedID and feedDate=@feedDate 
)t1 
group by t1.acctNo
OPTION(RECOMPILE)

Lors de l'exécution du test à partir de l'Analyseur de requêtes, j'ajoute les lignes suivantes:

declare @feedID int
select @feedID=20

declare @feedDate datetime
select @feedDate='1/2/2009'

Lors de son appel depuis mon programme C #, les paramètres sont transmis via la SqlCommand.Parameterspropriété.

Pour les besoins de cette discussion, vous pouvez supposer que les paramètres ne changent jamais afin que nous puissions exclure un paramètre sous-optimal sentant comme la cause.

Chad Decker
la source
3
Quels sont les paramètres de la requête? Consultez cet article. blogs.msdn.com/b/turgays/archive/2013/09/10/... Fondamentalement, SQL tente de générer le plan de requête basé sur des paramètres lorsque le processus est compilé pour la première fois. Cela peut générer un plan qui n'est pas optimal lorsque vous commencez à passer des paramètres différents, peut-être plus réalistes
Sparky
3
La requête est-elle suffisamment concise pour être répertoriée ici? Je pense que Sparky est correct et qu'il est probablement lié au reniflage de paramètres, j'ai eu un problème similaire qui m'a complètement dérouté jusqu'à la lecture de cet excellent article: sommarskog.se/query-plan-mysteries.html
Chris
1
Mais dans ce cas (pour le bien de ce test) je passe toujours les mêmes paramètres. Aucune autre application n'a pu se faufiler et appeler la requête en utilisant d'autres paramètres. Merci pour les articles. Examinera.
Chad Decker
2
Cela peut se produire soit parce qu'il renifle les valeurs des paramètres et des variables, soit parce qu'il fait de plus grandes simplifications. Des exemples de Simplifications seraient plus effondraient X = @X OR @X IS NULLà X=@Xet effectuer une cherchent Voir ici ou en poussant prédicats plus bas contre une vue avec des fonctions de fenêtre
Martin Smith
3
Après votre modification, l'exemple de l'Analyseur de requêtes utilise des variables, pas des paramètres. la valeur de ceux-ci n'est jamais reniflée sauf avec RECOMPILE. Dans tous les cas, saisissez les plans d'exécution et examinez les différences.
Martin Smith

Réponses:

157

Il y a des moments où l'utilisation a du OPTION(RECOMPILE)sens. D'après mon expérience, le seul moment où c'est une option viable est lorsque vous utilisez SQL dynamique. Avant d'explorer si cela a du sens dans votre situation, je vous recommande de reconstruire vos statistiques. Cela peut être fait en exécutant ce qui suit:

EXEC sp_updatestats

Et puis recréer votre plan d'exécution. Cela garantira que lorsque votre plan d'exécution sera créé, il utilisera les dernières informations.

L'ajout OPTION(RECOMPILE)reconstruit le plan d'exécution chaque fois que votre requête s'exécute. Je n'ai jamais entendu cela décrit comme, creates a new lookup strategymais peut-être que nous utilisons simplement des termes différents pour la même chose.

Lorsqu'une procédure stockée est créée (je soupçonne que vous appelez ad-hoc sql à partir de .NET, mais si vous utilisez une requête paramétrée, cela finit par être un appel proc stocké ) SQL Server tente de déterminer le plan d'exécution le plus efficace pour cette requête en fonction des données de votre base de données et des paramètres transmis ( détection des paramètres ), puis met en cache ce plan. Cela signifie que si vous créez la requête dans laquelle il y a 10 enregistrements dans votre base de données, puis que vous l'exécutez lorsqu'il y a 100 000 000 d'enregistrements, le plan d'exécution mis en cache peut ne plus être le plus efficace.

En résumé - je ne vois aucune raison pour laquelle ce OPTION(RECOMPILE)serait un avantage ici. Je pense que vous avez juste besoin de mettre à jour vos statistiques et votre plan d'exécution. La reconstruction des statistiques peut être une partie essentielle du travail de DBA en fonction de votre situation. Si vous rencontrez toujours des problèmes après la mise à jour de vos statistiques, je vous suggère de publier les deux plans d'exécution.

Et pour répondre à votre question - oui, je dirais qu'il est très inhabituel que votre meilleure option soit de recompiler le plan d'exécution chaque fois que vous exécutez la requête.

Abe Miessler
la source
22
Oui, sp_updatestats a fait l'affaire. Vous avez frappé dans le mille lorsque vous avez mentionné une requête exécutée initialement sur une table avec 10 enregistrements, et maintenant la table contient des millions d'enregistrements. C'était exactement mon cas. Je ne l'ai pas mentionné dans le post parce que je ne pensais pas que cela importait. Des trucs fascinants. Merci encore.
Chad Decker
3
C'est le seul moyen que j'ai trouvé de travailler avec des variables de table, car SQL pense toujours qu'il n'y a qu'une seule ligne. Quand il contient plusieurs milliers de lignes, cela devient un problème.
Alex Zhukovskiy
4
Un détail intéressant: la mise à jour des statistiques invalide implicitement tous les plans mis en cache qui utilisent ces statistiques, mais seulement si les statistiques ont réellement changé après l'action de mise à jour . Donc, pour les tables en lecture seule fortement biaisées, il semble qu'une explicite OPTION (RECOMPILE)pourrait être la seule solution.
Groo
141

Souvent, lorsqu'il y a une différence drastique d'une exécution à l'autre d'une requête, je trouve que c'est souvent l'un des 5 problèmes.

  1. STATISTIQUES- Les statistiques sont obsolètes. Une base de données stocke des statistiques sur la plage et la distribution des types de valeurs dans diverses colonnes des tables et des index. Cela aide le moteur de requête à développer un «plan» d'attaque sur la façon dont il va effectuer la requête, par exemple le type de méthode qu'il utilisera pour faire correspondre les clés entre les tables en utilisant un hachage ou en parcourant l'ensemble complet. Vous pouvez appeler Update Statistics sur l'ensemble de la base de données ou uniquement sur certaines tables ou index. Cela ralentit la requête d'une exécution à une autre car lorsque les statistiques sont obsolètes, il est probable que le plan de requête ne soit pas optimal pour les données nouvellement insérées ou modifiées pour la même requête (expliqué plus loin ci-dessous). Il peut ne pas être approprié de mettre à jour les statistiques immédiatement sur une base de données de production car il y aura une surcharge, un ralentissement et un décalage en fonction de la quantité de données à échantillonner. Vous pouvez également choisir d'utiliser une analyse complète ou un échantillonnage pour mettre à jour les statistiques. Si vous regardez le plan de requête, vous pouvez également afficher les statistiques sur les index en cours d'utilisation en utilisant la commandeDBCC SHOW_STATISTICS (nom de table, nom d'index) . Cela vous montrera la distribution et les plages des clés sur lesquelles le plan de requête utilise pour baser son approche.

  2. PARAMETER SNIFFING - Le plan de requête mis en cache n'est pas optimal pour les paramètres particuliers que vous transmettez, même si la requête elle-même n'a pas changé. Par exemple, si vous transmettez un paramètre qui ne récupère que 10 sur 1000000 lignes, le plan de requête créé peut utiliser une jointure par hachage, mais si le paramètre que vous transmettez utilise 750000 des 1000000 lignes, le plan créé peut être un analyse d'index ou analyse de table. Dans une telle situation, vous pouvez indiquer à l'instruction SQL d'utiliser l'option OPTION (RECOMPILE) ou un SP à utiliser WITH RECOMPILE. Dire au moteur qu'il s'agit d'un "plan à usage unique" et ne pas utiliser un plan mis en cache qui ne s'applique probablement pas. Il n'y a pas de règle sur la façon de prendre cette décision, cela dépend de la connaissance de la manière dont la requête sera utilisée par les utilisateurs.

  3. INDEXES - Il est possible que la requête n'ait pas changé, mais un changement ailleurs tel que la suppression d'un index très utile a ralenti la requête.

  4. ROWS CHANGED - Les lignes que vous interrogez changent radicalement d'un appel à l'autre. Les statistiques sont généralement mises à jour automatiquement dans ces cas. Cependant, si vous créez du SQL dynamique ou que vous appelez du SQL dans une boucle serrée, il est possible que vous utilisiez un plan de requête obsolète basé sur le mauvais nombre drastique de lignes ou de statistiques. Encore une fois dans ce cas OPTION (RECOMPILE) est utile.

  5. LA LOGIQUE C'est la logique, votre requête n'est plus efficace, c'était bien pour un petit nombre de lignes, mais plus d'échelle. Cela implique généralement une analyse plus approfondie du plan de requête. Par exemple, vous ne pouvez plus faire les choses en vrac, mais vous devez fragmenter les choses et faire des Commits plus petits, ou votre produit croisé était bien pour un ensemble plus petit mais prend maintenant du processeur et de la mémoire à mesure qu'il évolue plus grand, cela peut aussi être vrai pour en utilisant DISTINCT, vous appelez une fonction pour chaque ligne, vos correspondances clés n'utilisent pas d'index à cause de la conversion de type CASTING ou de NULLS ou de fonctions ... Trop de possibilités ici.

En général, lorsque vous écrivez une requête, vous devez avoir une idée de la façon dont certaines données sont distribuées dans votre table. Une colonne, par exemple, peut avoir un nombre uniformément réparti de valeurs différentes, ou elle peut être biaisée, 80% du temps avoir un ensemble spécifique de valeurs, que la distribution varie fréquemment dans le temps ou soit assez statique. Cela vous donnera une meilleure idée de la façon de créer une requête efficace. Mais aussi lors du débogage, les performances des requêtes ont une base pour construire une hypothèse expliquant pourquoi elles sont lentes ou inefficaces.

CodeCowboyOrg
la source
2
Merci mon ami. C'est une excellente information. Je n'aurais pas été en mesure de comprendre votre réponse lorsque j'ai initialement publié ma question, mais maintenant, cela me semble parfaitement logique.
Chad Decker
3
Le SNIFFING DE PARAMÈTRES est de loin le plus grand fléau de mon existence. Je ne connaissais même pas cette commande jusqu'à ce qu'une question d'entretien ait échoué. Ma solution pour le reniflage de paramètres a toujours été de hacher les valeurs des paramètres et d'ajouter "AND {hash} = {hash}" afin que le SQL soit toujours différent pour différentes valeurs. Un hack, mais ça a marché.
Jeremy Boyd
27

Pour ajouter à l'excellente liste (donnée par @CodeCowboyOrg) des situations où OPTION (RECOMPILE) peut être très utile,

  1. Variables de table . Lorsque vous utilisez des variables de table, il n'y aura pas de statistiques prédéfinies pour la variable de table, ce qui entraîne souvent de grandes différences entre les lignes estimées et réelles dans le plan de requête. L'utilisation d'OPTION (RECOMPILE) sur les requêtes avec des variables de table permet de générer un plan de requête qui a une bien meilleure estimation des numéros de lignes impliqués. J'ai eu une utilisation particulièrement critique d'une variable de table qui était inutilisable, et que j'allais abandonner, jusqu'à ce que j'ajoute OPTION (RECOMPILE). Le temps d'exécution est passé de quelques heures à quelques minutes. C'est probablement inhabituel, mais dans tous les cas, si vous utilisez des variables de table et travaillez sur l'optimisation, cela vaut la peine de voir si OPTION (RECOMPILE) fait une différence.
DWright
la source
1
J'ai une requête avec 5 variables de table. Sur ma machine, il s'exécute pendant plus d'une demi-heure. Sur la machine de mon collègue, il s'exécute en <1 seconde. Les machines ont un matériel similaire et la même version de SQL Server. Si nous ajoutons tous les deux OPTION (RECOMPILE), alors il s'exécute en 2 secondes sur les deux machines. Dans tous les cas, le test d'exécution est effectué dans SSMS. Qu'est-ce qui pourrait causer cette différence?
Adam
1
Pouvez-vous comparer le plan d'exécution pour celui-ci sur votre machine et sur la machine de vos collègues sans Option (recompiler)? Cela pourrait montrer la source de la différence.
DWright
1
pour les tables temporaires, c'est la même situation?
Muflix
1
@muflix: bonne question. Je ne pense pas que l'effet soit le même pour les tables temporaires, car elles ont des statistiques et le moteur devrait faire des choix de recompilation automatique comme pour les autres tables, je crois (mais je ne suis pas certain). Peut-être que quelqu'un d'autre le sait avec plus de certitude.
DWright
2
Les statistiques dans les tables temporaires ne sont pas automatiquement mises à jour ou recompilées, le programmeur doit donc le faire.
J. Michael Wuerth
1

La toute première action avant de régler les requêtes consiste à défragmenter / reconstruire les index et les statistiques, sinon vous perdez votre temps.

Vous devez vérifier le plan d'exécution pour voir s'il est stable (c'est le même lorsque vous modifiez les paramètres), sinon, vous devrez peut-être créer un index de couverture (dans ce cas pour chaque table) (connaissant le système, vous pouvez en créer un qui est également utile pour d'autres requêtes).

comme exemple: créer l'index idx01_datafeed_trans On datafeed_trans (feedid, feedDate) INCLUDE (acctNo, tradeDate)

si le plan est stable ou si vous pouvez le stabiliser, vous pouvez exécuter la phrase avec sp_executesql ('phrase sql') pour enregistrer et utiliser un plan d'exécution fixe.

si le plan est instable, vous devez utiliser une instruction ad hoc ou EXEC ('phrase sql') pour évaluer et créer un plan d'exécution à chaque fois. (ou une procédure stockée "avec recompilation").

J'espère que ça aide.

Cristian Solervicéns
la source
1

Necroing cette question mais il y a une explication que personne ne semble avoir envisagée.

STATISTIQUES - Les statistiques ne sont pas disponibles ou trompeuses

Si toutes les conditions suivantes sont vraies:

  1. Les colonnes feedid et feedDate sont susceptibles d'être fortement corrélées (par exemple, un identifiant de flux est plus spécifique qu'une date de flux et le paramètre de date est une information redondante).
  2. Il n'y a pas d'index avec les deux colonnes en tant que colonnes séquentielles.
  3. Il n'y a pas de statistiques créées manuellement couvrant ces deux colonnes.

Ensuite, le serveur SQL peut supposer à tort que les colonnes ne sont pas corrélées, ce qui entraîne des estimations de cardinalité inférieures aux prévisions pour l'application des deux restrictions et la sélection d'un plan d'exécution médiocre. Le correctif dans ce cas serait de créer un objet de statistiques reliant les deux colonnes, ce qui n'est pas une opération coûteuse.

MonkeyPushButton
la source