2009-04-22 11 views
2

J'ai donc ce problème étrange avec une procédure stockée SQL Server. Fondamentalement, j'ai cette procédure longue et complexe. Quelque chose comme ceci:Problème de requête Weird SQL Server

SELECT Table1.col1, Table2.col2, col3 
FROM Table1 INNER JOIN Table2 
    Table2 INNER JOIN Table3 
    ----------------------- 
    ----------------------- 
    (Lots more joins) 
WHERE Table1.Col1 = dbo.fnGetSomeID() AND (More checks) 
    ----------------------- 
    ----------------------- 
(6-7 More queries like this with the same check) 

Le problème est que l'enregistrement dans la clause WHERE à la fin Table1.Col1 = dbo.fnGetSomeID(). La fonction dbo.fnGetSomeID() renvoie une valeur entière simple . Donc, quand je code en dur la valeur où l'appel de fonction devrait être le SP prend seulement environ 15 secondes. MAIS quand je le remplace par cet appel de fonction dans la clause WHERE, il faut environ 3,5 minutes.

donc je fais ceci:

DECLARE @SomeValue INT 
SET @SomeValue = dbo.fnGetSomeID() 
--Where clause changed 
WHERE Table1.Col1 = @SomeValue 

Alors maintenant, la fonction est appelée une seule fois. Mais toujours les mêmes 3,5 minutes. Donc, je vais de l'avant et fais ceci:

DECLARE @SomeValue INT 
--Removed the function, replaced it with 1 
SET @SomeValue = 1 
--Where clause changed 
WHERE Table1.Col1 = @SomeValue 

Et encore ça prend 3.5 minutes. Pourquoi l'impact sur les performances? Et comment le faire disparaître?

+0

Combien de temps faut-il pour exécuter 'dbo.fnGetSomeID()' seul –

+0

Moins d'une seconde. –

+0

Mais cela ne devrait pas importer parce que je remplace par la valeur codée en dur à la fin. –

Répondre

0

Une autre chose à essayer. Au lieu de charger l'identifiant dans une variable, le charger dans une table

if object_id('myTable') is not null drop myTable 
select dbo.fnGetSomeID() as myID into myTable 

puis utilisez

WHERE Table1.Col1 = (select myID from myTable) 

dans votre requête.

1

Comme cela est mentionné ailleurs, il y aura des différences de plan d'exécution selon l'approche que vous prenez. Je regarderais les deux plans d'exécution pour voir s'il y a une réponse évidente là-bas.

This question décrit un problème similaire, et la réponse dans ce cas s'est avérée impliquer des paramètres de connexion.

J'ai couru aussi dans presque moi-même exactesame problem comme cela, et ce que je trouve dans ce cas est que l'utilisation des constructions plus récentes (fonctions analytiques dans SQL 2008) a apparemment été source de confusion l'optimiseur. Cela peut ne pas être le cas pour vous, car vous utilisez SQL 2005, mais quelque chose de similaire peut se produire en fonction du reste de votre requête. Une autre chose à regarder est de savoir si vous avez une distribution biaisée des valeurs pour Table1.Col1 - si l'optimiseur utilise un plan d'exécution général lorsque vous utilisez la fonction ou la variable plutôt que la constante, cela pourrait conduire pour choisir des jointures sous-optimales que quand il peut clairement voir que la valeur est une constante spécifique. Si tout le reste échoue et que cette requête ne se trouve pas dans une autre UDF, vous pouvez précalculer la valeur UDF de fnGetSomeID() comme vous le faisiez, puis encapsuler la requête entière en SQL dynamique, fournissant la valeur comme constante dans le SQL. chaîne. Cela devrait vous donner des performances plus rapides, au prix d'une recompilation de la requête à chaque fois (ce qui devrait être un bon trade dans ce cas).

2

Même avec @SomeValue fixé à 1, lorsque vous avez

WHERE Table1.Col1 = @SomeValue 

SQL Server considère probablement encore @SomeValue comme une variable, non pas comme un 1 hardcoded, et qui aurait une incidence sur le plan de requête en conséquence. Et comme Table1 est liée à Table2 et Table2 est liée à Table3, etc., la durée d'exécution de la requête est agrandie. D'autre part, lorsque vous avez

WHERE Table1.Col1 = 1 

Le plan de requête est verrouillé avec Table1.Col1 à une valeur constante de 1. Juste parce que nous voyons

WHERE Table1.Col1 = @SomeValue 

comme « hardcoding », n » t signifie que SQL le voit de la même manière. Chaque produit cartésien possible est candidat et @SomeValue doit être évalué pour chacun. Ainsi, les recommandations standard s'appliquent - vérifiez votre plan d'exécution, réécrivez la requête si nécessaire.

De plus, ces colonnes de jointure sont-elles indexées?

+0

Oui, ils sont indexés. –

0

Vous pouvez essayer l'indicateur OPTIMIZE FOR pour forcer un plan pour une constante donnée, mais il peut avoir des résultats incohérents; en 2008 vous pouvez utiliser OPTIMIZE FOR UNKNOWN

0

Je pense que puisque l'optimiseur n'a aucune idée du travail de la fonction, il essaie de les évaluer en dernier.

Je voudrais essayer de stocker la valeur de retour de la fonction dans une variable à l'avance, et en utilisant cela dans votre clause where.

En outre, vous pouvez essayer la liaison de schéma de votre fonction, car apparemment parfois seriously affects peformance.

Vous pouvez rendre votre schéma fonctionnel lié comme ceci:

create function fnGetSomeID() 
with schema_binding 
returns int 
... etc. 
0
(Lots more joins) 

OÙ Table1. Col1 = dbo.fnGetSomeID() AND (Autres vérifications)

Ce n'est pas un bon problème à avoir. Peu importe, enfin, si la valeur est retournée par une fonction ou une sous-requête ou une variable ou est une constante. Mais c'est le cas, et à un certain niveau de complexité, il est très difficile d'obtenir des résultats cohérents. Et vous ne pouvez pas vraiment le déboguer, car ni vous ni personne d'autre ici ne peuvent scruter la boîte noire qui est l'optimiseur de requête. Tout ce que vous pouvez faire, c'est le pousser et voir comment il se comporte.

Je pense que l'optimiseur de requête se comporte de manière erratique, car il y a beaucoup de tables dans la requête. Quand vous lui dites de chercher 1 il regarde les statistiques d'index et fait un bon choix. Lorsque vous lui dites autre chose, il suppose qu'il devrait se joindre en fonction de ce qu'il sait, ne faisant pas confiance à votre fonction/variable pour retourner une valeur sélective. Pour que cela soit vrai, Table1.Col1 doit avoir une distribution inégale des valeurs. Ou l'optimiseur de requête n'est pas, um, optimal.

Dans les deux cas, le plan de requête estimé devrait montrer une différence. Recherchez des opportunités pour ajouter (ou, parfois, supprimer) un index. Il pourrait être le plan 3.5 est raisonnable dans beaucoup de cas, et ce que le serveur veut vraiment, c'est de meilleurs index.

Au-delà, il y a des conjectures. Parfois, c'est triste à dire, la réponse consiste à trouver le sous-ensemble de tables qui produisent un petit ensemble de lignes, en les plaçant dans une table temporaire, et en joignant cela au reste des tables. L'indice OPTIMIZE FOR pourrait aussi être utile. Gardez à l'esprit, cependant, que toute solution que vous venez avec sera fragile, dépend des données et de la version.