web-dev-qa-db-fra.com

OPTION (RECOMPILE) est toujours plus rapide; Pourquoi?

J'ai rencontré une situation étrange dans laquelle l'ajout de OPTION (RECOMPILE) à ma requête s'exécutait en une demi-seconde, alors que l'omettre entraînait un dépassement de plus de cinq minutes de la requête.

C'est le cas lorsque la requête est exécutée à partir de l'Analyseur de requêtes ou de mon programme C # via SqlCommand.ExecuteReader(). Appeler (ou ne pas appeler) DBCC FREEPROCCACHE ou DBCC dropcleanbuffers ne 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 elle. La requête est toujours appelée avec les mêmes paramètres [dans l’intérêt de ce test].

J'utilise SQL Server 2008.

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

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

Désolé pour la question d'entrée de gamme, mais je ne peux pas vraiment en faire autant.

PDATE: On m'a demandé de poster 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 l'appel de mon programme C #, les paramètres sont transmis via la propriété SqlCommand.Parameters.

Pour les besoins de cette discussion, vous pouvez supposer que les paramètres ne changent jamais, nous pouvons donc exclure que les paramètres soient sous-optimaux en tant que cause.

146
Chad Decker

Il est parfois logique d'utiliser OPTION(RECOMPILE). D'après mon expérience, la seule option viable est l'utilisation de SQL dynamique. Avant de vous demander si cela a du sens dans votre situation, je vous recommanderais de reconstruire vos statistiques. Cela peut être fait en lançant ce qui suit:

EXEC sp_updatestats

Et puis recréer votre plan d'exécution. Cela garantira que, lors de la création de votre plan d'exécution, il utilisera les informations les plus récentes.

L'ajout de OPTION(RECOMPILE) reconstruit le plan d'exécution à chaque exécution de votre requête. Je n'ai jamais entendu parler de cela sous la forme creates a new lookup strategy mais peut-être utilisons-nous simplement des termes différents pour la même chose.

Lorsqu'une procédure stockée est créée (je suppose que vous appelez SQL ad-hoc à partir de .NET mais si vous utilisez une requête paramétrée, il s'agit en l'occurrence d'un appel proc stocké ) SQL Server tente de déterminer le Le plan d’exécution le plus efficace pour cette requête est basé sur les données de votre base de données et les paramètres entrés ( recherche de paramètre ), puis met en cache ce plan. Cela signifie que si vous créez la requête contenant 10 enregistrements dans votre base de données, puis l'exécutez lorsqu'il y a 100 000 000 enregistrements, le plan d'exécution mis en cache risque de ne plus être efficace.

En résumé - je ne vois aucune raison pour que OPTION(RECOMPILE) soit un avantage ici. Je suppose que vous devez simplement mettre à jour vos statistiques et votre plan d’exécution. La reconstruction des statistiques peut constituer un élément essentiel du travail de l'administrateur de base de données, selon votre situation. Si vous rencontrez toujours des problèmes après la mise à jour de vos statistiques, je vous conseillerais 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.

138
Abe Miessler

Souvent, quand il y a une différence radicale d'un cycle à l'autre d'une requête, je trouve que c'est souvent l'un des cinq problèmes.

  1. STATISTICS - 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 sur les tables et les index. Cela aide le moteur de requête à développer un "plan" d'attaque indiquant comment il va exécuter la requête, par exemple le type de méthode qu'il utilisera pour faire correspondre les clés entre les tables à l'aide d'un hachage ou en parcourant l'ensemble. Vous pouvez appeler Statistiques de mise à jour sur la base de données entière ou uniquement sur certaines tables ou index. Cela ralentit la requête d'une exécution à l'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 récemment insérées ou modifiées pour la même requête (expliqué plus en détail 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 des frais généraux, 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 examinez le plan de requête, vous pouvez également afficher les statistiques sur les index utilisés, par exemple, à l'aide de la commande DBCC SHOW_STATISTICS (nom_table, nom_index). Cela vous montrera la distribution et les plages des clés utilisées par le plan de requête 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 extrait seulement 10 lignes sur 1 000 000, le plan de requête créé peut utiliser une jointure de hachage. Toutefois, si le paramètre que vous transmettez utilise 750 000 des 1 000 000 lignes, le plan créé peut être une balayage d'index ou de table. Dans une telle situation, vous pouvez indiquer à l'instruction SQL d'utiliser l'option OPTION (RECOMPILE) ou un SP à utiliser avec WITH RECOMPILE. Indiquer 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 manière de prendre cette décision, cela dépend de la façon 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. Habituellement, les statistiques sont automatiquement mises à jour dans ces cas. Toutefois, si vous créez du SQL dynamique ou appelez SQL dans une boucle serrée, il est possible que vous utilisiez un plan de requête obsolète basé sur un nombre incorrect 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, elle convient à un petit nombre de lignes, mais n'est plus à l'échelle. Cela implique généralement une analyse plus approfondie du plan de requête. Par exemple, vous ne pouvez plus faire des tâches en bloc, vous devez découper des choses et effectuer des commits plus petits, ou votre produit croisé convient à un jeu plus petit, mais utilise maintenant plus de ressources processeur et de mémoire à mesure qu’il s’agrandit, cela peut également être vrai pour. en utilisant DISTINCT, vous appelez une fonction pour chaque ligne, vos correspondances de clé n'utilisent pas d'index en raison 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 devriez avoir une idée mentale 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 asymétrique, 80% du temps a un ensemble spécifique de valeurs, que la distribution varie fréquemment dans le temps ou qu'elle soit relativement statique. Cela vous donnera une meilleure idée de la manière de construire une requête efficace. Toutefois, lors du débogage, les performances des requêtes ont également une base pour construire une hypothèse expliquant pourquoi elles sont lentes ou inefficaces.

125
CodeCowboyOrg

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înera souvent des différences importantes entre les lignes estimées et réelles dans le plan de requête. L'utilisation de OPTION (RECOMPILE) sur des requêtes avec des variables de table permet de générer un plan de requête offrant une estimation bien meilleure des numéros de ligne impliqués. J'avais 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 à l'optimisation, il est utile de voir si OPTION (RECOMPILE) fait la différence.
24
DWright

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 (identique lorsque vous modifiez les paramètres). Sinon, vous devrez peut-être créer un index de couverture (dans ce cas pour chaque table) (sachant que le système vous permet d'en créer un est utile pour d'autres requêtes aussi).

à titre d'exemple: create index idx01_datafeed_trans Sur datafeed_trans (feedid, feedDate) INCLUDE (acctNo, tradeDate)

si le plan est stable ou que 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 recompiler").

J'espère que ça aide.

0

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

STATISTIQUES - Les statistiques ne sont pas disponibles ou sont trompeuses

Si tout ce qui suit est vrai:

  1. Les colonnes feedid et feedDate sont susceptibles d'être fortement corrélées (par exemple, un identifiant de fil est plus spécifique qu'une date de fil et le paramètre de date est une information redondante).
  2. Il n'y a pas d'index avec les deux colonnes sous forme de colonnes séquentielles.
  3. Aucune statistique créée manuellement ne couvre ces deux colonnes.

Ensuite, il se peut que SQL Server suppose à tort que les colonnes ne sont pas corrélées, ce qui conduit à 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. La solution 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.

0
MonkeyPushButton