Les fonctions de la fenêtre provoquent un plan d'exécution horrible lorsqu'elles sont appelées à partir d'une vue avec une clause externe "where" paramétrée

10

J'ai eu ce problème il y a longtemps, j'ai trouvé une solution de contournement qui me convenait et je l'ai oubliée.

Mais maintenant, il y a cette question sur SO donc je suis prêt à soulever ce problème.

Il y a une vue qui joint quelques tables de manière très simple (commandes + lignes de commande).

Lorsqu'elle est interrogée sans whereclause, la vue renvoie plusieurs millions de lignes.
Cependant, personne ne l'appelle jamais comme ça. La requête habituelle est

select * from that_nasty_view where order_number = 123456;

Cela renvoie environ 10 enregistrements sur 5m.

Une chose importante: la vue contient une fonction de fenêtre rank(), qui est partitionnée exactement par le champ à l'aide duquel la vue est toujours interrogée:

rank() over (partition by order_number order by detail_line_number)

Maintenant, si cette vue est interrogée avec des paramètres littéraux dans la chaîne de requête, exactement comme indiqué ci-dessus, elle retourne les lignes instantanément. Le plan d'exécution est très bien:

  • Recherche d'index sur les deux tables en utilisant les indices sur order_number(retourne 10 lignes).
  • Calcul des fenêtres sur le minuscule résultat renvoyé.
  • Sélection.

Cependant, lorsque la vue est appelée de manière paramétrée, les choses deviennent désagréables:

  • Index scansur toutes les tables en ignorant les indices. Renvoie 5 m de lignes.
  • Énorme jointure.
  • Calcul des fenêtres sur tous les partitions (environ 500k fenêtres).
  • Filter prendre 10 rangs sur 5m.
  • Sélectionner

Cela se produit dans tous les cas lorsque des paramètres sont impliqués. Il peut s'agir de SSMS:

declare @order_number int = 123456;
select * from that_nasty_view where order_number = @order_number;

Il peut s'agir d'un client ODBC, tel qu'Excel:

select * from that_nasty_view where order_number = ?

Ou il peut s'agir de tout autre client qui utilise des paramètres et non la concaténation sql.

Si la fonction de fenêtre est supprimée de la vue, elle s'exécute parfaitement rapidement, qu'elle soit ou non interrogée avec des paramètres.

Ma solution de contournement consistait à supprimer la fonction incriminée et à la réappliquer ultérieurement.

Mais qu'est-ce qui donne? Est-ce vraiment un bug dans la façon dont SQL Server 2008 gère les fonctions des fenêtres?

GSerg
la source
order_number est la clé primaire? Les types de données de la colonne et du paramètre correspondent-ils?
gbn
order_numbern'est pas une clé primaire. C'est int not nullavec un index non clusterisé dans les deux tables.
GSerg
5
SQL Server 2005 a rencontré des problèmes avec la transmission des prédicats dans ce domaine. Je pensais qu'ils étaient corrigés maintenant. BTW votre exemple TSQL utilise une variable et non un paramètre. Est-ce que l'ajout d' OPTION (RECOMPILE)aide?
Martin Smith
1
@GSerg - Donc, sur le mauvais plan avec le dernier filtre, il y a environ 5 millions de lignes dans le filtre et environ 10 lignes correspondant au réel? Si c'est le cas, c'est peut-être que le problème de poussée des prédicats n'est pas encore complètement résolu.
Martin Smith

Réponses:

5

Cela semble être un problème de longue date qui continue de refaire surface sous une forme ou une autre et qui est toujours présent dans SQL Server 2012.

Certains messages en discutant sont

Toutes les versions actuelles de SQL Server jusqu'en 2012 inclus ne sont pas en mesure de pousser le filtre sur un groupe de partitionnement après le projet de séquence pour un prédicat paramétré, sauf s'il option(recompile)est utilisé (si 2008+).

Une alternative à l' recompileindice serait de réécrire la requête pour utiliser un TVF en ligne paramétré comme suggéré par @ a1ex07)

Martin Smith
la source
Juste eu le cas dans SQL Server 2014 aussi
Guillaume86
3

J'essaierais de remplacer la vue par un udf de valeur table. De cette façon, il filtrera d'abord les enregistrements, puis appliquera la fonction de fenêtre. Cette fonction peut accepter le paramètre de table de sorte que vous pouvez passer plusieurs order_numberdans ce

a1ex07
la source
Encore une autre solution de contournement, oui. Je ne pouvais pas le faire car tous les clients n'étaient pas en mesure de consommer une fonction table.
GSerg
Pourquoi? Je ne suis pas sûr à 100%, mais je pense que tout ce dont vous avez besoin est de changer un peu la requête en quelque chose commeSELECT * FROM my_funct(12345)
a1ex07
L'une des exigences était que la requête soit consommable par les utilisateurs finaux utilisant Excel (c'est-à-dire par MS Query), et MS Query ne vous le permettra pas, au moins dans les versions jusqu'en 2003.
GSerg
it will filter records first, and then apply window functionest incorrect. Il n'y a pas d'ordre d'exécution déterministe
Remus Rusanu