web-dev-qa-db-fra.com

Requête SQL Server: rapide avec littéral mais lente avec variable

J'ai une vue qui retourne 2 ints d'une table en utilisant un CTE. Si j'interroge la vue comme celle-ci, elle s'exécute en moins d'une seconde

SELECT * FROM view1 WHERE ID = 1

Cependant, si j'interroge la vue comme cela, cela prend 4 secondes.

DECLARE @id INT = 1
SELECT * FROM View1 WHERE ID = @id

J'ai vérifié les 2 plans de requête et la première requête effectue une recherche d'index clusterisé sur la table principale, renvoyant 1 enregistrement puis appliquant le reste de la requête d'affichage à cet ensemble de résultats, la seconde requête effectuant alors une analyse d'index. renvoyer environ 3000 enregistrements plutôt que seulement celui qui m'intéresse, puis filtrer plus tard l'ensemble de résultats.

Y a-t-il quelque chose d'évident qui me manque pour que la deuxième requête utilise la recherche d'index plutôt qu'une analyse d'index? J'utilise SQL 2008 mais tout ce que je fais doit également être exécuté sur SQL 2005. Au début, je pensais qu'il s'agissait d'un problème de détection de paramètre, mais j'obtiens les mêmes résultats même si j'efface le cache.

31
Gavin

C'est probablement parce que, dans le cas du paramètre, l'optimiseur ne peut pas savoir que la valeur n'est pas nulle. Il doit donc créer un plan qui renvoie des résultats corrects, même s'il l'est. Si vous avez SQL Server 2008 SP1, vous pouvez essayer d'ajouter OPTION(RECOMPILE) à la requête.

32
erikkallen

Dans mon cas, le type de colonne de la table de base de données était défini en tant que VarChar et le type de paramètre de requête paramétré en tant que NVarChar, ceci introduisait CONVERT_IMPLICIT dans le plan d'exécution réel afin de faire correspondre le type de données avant la comparaison, ce qui était préjudiciable aux performances des truies, 2 à 11 secondes . Il suffit de corriger le type de paramètre pour que la requête paramétrée soit aussi rapide que la version non paramétrée.

J'espère que cela pourra aider quelqu'un avec un problème similaire.

2
Morbia

Vous pouvez ajouter un OPTIMIZE FOR conseil à votre requête, par exemple.

DECLARE @id INT = 1
SELECT * FROM View1 WHERE ID = @id OPTION (OPTIMIZE FOR (@ID = 1))
2

Lorsque SQL commence à optimiser le plan de requête pour la requête avec la variable, il fait correspondre l'index disponible à la colonne. Dans ce cas, il existait un index afin que SQL ait pensé qu'il ne ferait que scanner l'index à la recherche de la valeur. Lorsque SQL établissait le plan de la requête avec la colonne et une valeur littérale, il pouvait consulter les statistiques et la valeur pour décider s'il fallait analyser l'index ou si une recherche serait correcte. 

L'utilisation de l'indicateur d'optimisation et d'une valeur indique à SQL que «c'est la valeur qui sera utilisée la plupart du temps, alors optimisez-la pour cette valeur» et un plan est stocké comme si cette valeur littérale avait été utilisée. L'utilisation de l'indicateur d'optimisation et du sous-indice de UNKNOWN indique à SQL que vous ne savez pas quelle sera la valeur. SQL examine donc les statistiques de la colonne et décide ce qui, rechercher ou analyser, sera le meilleur et établit le plan en conséquence. 

1
RC_Cleland

J'ai moi-même rencontré ce problème avec une vue dont la durée d'exécution était <10 ms avec une affectation directe ( WHERE UtilAcctId = 12345 ), mais qui a été multipliée par 100 avec une affectation de variable ( WHERE UtilAcctId = @UtilAcctId ) .
Le plan d’exécution de cette dernière n’était pas différent de celui que j’avais exécuté la vue sur toute la table. 

Ma solution n'a pas nécessité des tonnes d'index, des conseils d'optimisation ou une mise à jour longue de statistiques. 

Au lieu de cela, j'ai converti la vue en User-Table-Function où le paramètre était la valeur requise par la clause WHERE. En fait, cette clause WHERE était imbriquée dans 3 requêtes, elle fonctionnait toujours et revenait à la vitesse <10ms. 

Finalement, j'ai changé le paramètre pour qu'il soit un type qui est une table de UtilAcctIds (int). Ensuite, je peux limiter la clause WHERE à une liste de la table. WHERE UtilAcctId = [Liste de paramètres] .UtilAcctId. Cela fonctionne encore mieux. Je pense que les fonctions de la table utilisateur sont précompilées.

1
Michael Barash

Je suis tombé sur ce même problème et il s'est avéré qu'il manquait un index impliquant une jointure (à gauche) sur le résultat d'une sous-requête.

select *
from foo A
left outer join (
  select x, count(*)
  from bar
  group by x
) B on A.x = B.x

Ajout d'un index nommé bar_x pour bar.x

0
Sandman

DECLARE @id INT = 1

SELECT * FROM View1 WHERE ID = @id

Faire ceci

DECLARE @sql varchar (max)

SET @ sql = 'SELECT * FROM View1 WHERE ID =' + CAST (@id as varchar)

EXEC (@sql)

Résout votre problème

0
Neeraj kalyan