2009-09-08 6 views
10

Je sélectionne certaines lignes d'une fonction de valeur table mais j'ai trouvé une différence de performances massive inexplicable en plaçant SELECT TOP dans la requête.Différence de performances massives SQL avec SELECT TOP x même lorsque x est beaucoup plus élevé que les lignes sélectionnées

SELECT col1, col2, col3 etc 
FROM  dbo.some_table_function 
WHERE col1 = @parameter 
--ORDER BY col1 

prend plus de 5 ou 6 minutes pour terminer.

Cependant

SELECT TOP 6000 col1, col2, col3 etc 
FROM  dbo.some_table_function 
WHERE col1 = @parameter 
--ORDER BY col1 

en 4 termine ou 5 secondes.

Cela ne me surprendrait pas si l'ensemble de données renvoyé était énorme, mais la requête particulière impliquée renvoie ~ 5000 lignes sur 200 000. Donc, dans les deux cas, la totalité de la table est traitée, car SQL Server continue jusqu'à la fin en recherchant 6000 lignes auxquelles il n'arrivera jamais. Pourquoi la différence massive alors? Cela a-t-il quelque chose à voir avec la façon dont SQL Server alloue de l'espace en prévision de la taille de l'ensemble de résultats (le TOP 6000 lui donne donc une exigence faible qui est plus facilement allouée en mémoire)? Est-ce que quelqu'un d'autre a été témoin de quelque chose comme ça?

Merci

+0

Avez-vous regardé les plans de requête? Y a-t-il une différence? –

+2

Juste curieux, ce qui arrive à la performance si vous dites SELECT TOP 100 PERCENT ....? –

+0

Je suppose que vous avez des statistiques qui élimine l'optimiseur de requêtes de kelter. L'optimiseur peut, par exemple, décider d'utiliser une analyse de table au lieu d'une recherche d'index s'il pense qu'il y a très peu de lignes dans une table. Pourquoi cela ne concerne pas la requête TOP Je ne sais pas, mais examinons les plans d'exécution. Ceux-ci vous montrent ce que fait le serveur, et cela explique pourquoi on est lent. Il va également vous montrer le nombre estimé et réel de lignes. Si certaines estimations sont très éloignées, mettez à jour les statistiques et réessayez. :) –

Répondre

6

Les fonctions de table peuvent avoir un temps d'exécution non linéaire.

Considérons une fonction équivalente pour cette requête:

SELECT (
     SELECT SUM(mi.value) 
     FROM mytable mi 
     WHERE mi.id <= mo.id 
     ) 
FROM mytable mo 
ORDER BY 
     mo.value 

Cette requête (qui calcule la course SUM) est rapide au début et lent à la fin, car sur chaque ligne de mo il faut additionner toutes les les valeurs précédentes qui nécessitent le rembobinage de la source de lignes.

Le temps nécessaire pour calculer SUM pour chaque ligne augmente à mesure que le nombre de lignes augmente.

Si vous rendez mytable suffisamment grand (par exemple, 100,000 lignes, comme dans votre exemple) et exécutez cette requête, vous verrez que cela prend beaucoup de temps.

Toutefois, si vous appliquez TOP 5000 à cette requête, vous verrez qu'elle se termine beaucoup plus rapidement que 1/20 du temps requis pour la table complète. Très probablement, quelque chose de similaire se produit aussi dans votre cas.

Pour dire quelque chose de plus définitivement, j'ai besoin de voir la définition de la fonction.

Mise à jour:

SQL Server peut pousser prédicats dans la fonction.

Par exemple, je viens de créer cette TVF:

CREATE FUNCTION fn_test() 
RETURNS TABLE 
AS 
RETURN (
     SELECT * 
     FROM master 
     ); 

Ces requêtes:

SELECT * 
FROM fn_test() 
WHERE name = @name 

SELECT TOP 1000 * 
FROM fn_test() 
WHERE name = @name 

rendement différents plans d'exécution (le premier utilise de balayage en clusters, le second utilise un index cache avec TOP)

+0

'Fraid pas dans ce cas. Le point de ma requête est que les lignes _same_ sont retournées, que la clause TOP soit utilisée ou non (TOP 6000 étant plus grand que le jeu de résultats). Il ne peut donc pas avoir à faire avec le calcul de ces lignes elles-mêmes. – Ray

+0

'@ Arj': pourriez-vous poster votre définition de fonction? – Quassnoi

+0

@Quassnoi: le TVF en ligne est simplement une macro. – gbn

1

Il n'est pas nécessairement vrai que toute la table est traitée si col1 a un index.

L'optimisation SQL choisira d'utiliser ou non un index. Peut-être que votre «TOP» l'oblige à utiliser l'index.

Si vous utilisez MSSQL Query Analyzer (Le nom m'échappe), appuyez sur Ctrl-K. Cela montrera le plan d'exécution de la requête au lieu de l'exécuter. Mousser sur les icônes montrera l'utilisation IO/CPU, je crois. Je parie que l'un utilise une recherche d'index, tandis que l'autre ne l'est pas.

Si vous avez un client générique: SET SHOWPLAN_ALL ON; GO sélectionnez ...; aller

voir http://msdn.microsoft.com/en-us/library/ms187735.aspx pour plus de détails.

+0

Ouais - je regarde le plan en ce moment. Bien que j'ai modifié la requête pour l'affichage. En réalité, il fait SELECT *. Je ne peux pas voir comment l'utilisation de TOP provoquerait une utilisation de l'index? – Ray

+0

SQL Optimizer décidera d'utiliser ou non un index. J'ai fait des requêtes où la clause where provoque un "point de bascule" où l'optimiseur décide de faire une analyse complète de la table au lieu d'utiliser un index. – ericp

1

Vous êtes peut-être en train de vous lancer dans quelque chose d'aussi simple que la mise en cache ici - peut-être que (pour une raison quelconque) la requête "TOP" est mise en cache? En utilisant un index que l'autre ne l'est pas?

Dans tous les cas, le meilleur moyen d'étancher votre curiosité est d'examiner le plan d'exécution complet pour les deux requêtes. Vous pouvez le faire correctement dans SQL Management Console et il vous indiquera EXACTEMENT quelles opérations sont en cours et combien de temps chacun est prévu.

Toutes les implémentations SQL sont originales à leur manière - SQL Server ne fait pas exception. Ce genre de "whaaaaaa?!" les moments sont assez communs.

3

Votre TOP n'a pas de commande, donc c'est tout simplement le même que SET ROWCOUNT 6000 en premier. Un ORDER BY exigerait que toutes les lignes soient évaluées en premier, et cela prendrait beaucoup plus de temps.

Si dbo.some_table_function est une table en ligne de valeur udf, alors il s'agit simplement d'une macro qui est développée de sorte qu'elle renvoie les 6000 premières lignes comme indiqué dans aucun ordre particulier.

Si la valeur udf est multi-valuée, alors elle est une boîte noire et tirera toujours l'ensemble de données complet avant le filtrage. Je ne pense pas que cela arrive.

pas directement lié, mais another SO question on TVFs

1

Je pense que la suggestion de Quassnois semble très plausible. En ajoutant TOP 6000, vous indiquez implicitement à l'optimiseur qu'un petit sous-ensemble des 200 000 lignes va être renvoyé. L'optimiseur utilise ensuite une recherche d'index au lieu d'une analyse d'index en cluster ou d'une analyse de table.

Une autre explication possible pourrait mettre en cache, comme le suggère Jim Davis. C'est assez facile à exclure en réexécutant les requêtes. Essayez d'exécuter celui avec TOP 6000 en premier.

2

J'ai eu le même problème, une requête simple joignant cinq tables retournant 1000 lignes a pris deux minutes pour terminer. Quand j'ai ajouté "TOP 10000", il a terminé en moins d'une seconde. Il s'est avéré que l'index clusterisé sur l'une des tables était fortement fragmenté. Après la reconstruction de l'index, la requête se termine maintenant en moins d'une seconde.

Questions connexes