web-dev-qa-db-fra.com

SQL IN est-il mauvais pour les performances?

J'ai une requête faisant quelque chose comme:

SELECT FieldX, FieldY FROM A
WHERE FieldW IN (108, 109, 113, 138, 146, 160,
307, 314, 370, 371, 441, 454 ,457, 458, 479, 480,
485, 488, 490, 492, 519, 523, 525, 534, 539, 543,
546, 547, 550, 564, 573, 629, 642, 643, 649, 650,
651, 694, 698, 699, 761, 762, 768, 772, 773, 774,
775, 778, 784, 843, 844, 848, 851, 852, 853, 854,
855, 856, 857, 858, 859, 860, 861, 862, 863, 864,
865, 868, 869, 871, 872, 873, 891) 

Avoir une clause IN avec autant d'options, est-ce mauvais pour les performances des requêtes? Je rencontre de nombreux délais d'attente dans mon application et je pense que cela pourrait être une source de ce type de problème. Puis-je optimiser la requête sans supprimer les chiffres, en utilisant un bon conseil SQL?

ÉDITER:

@KM ce sont des clés dans une table différente. Il s'agit d'une application de forum, expliquant brièvement: c # récupère tous les forums de la base de données et les stocke dans le cache de l'application. Avant que C # appelle une procédure qui obtient les threads pour ces forums et pour cet utilisateur, c # effectue une logique de filtrage de la collection "tous les forums", en tenant compte des autorisations et de la logique métier. Le délai d'expiration se produit sur la base de données et non sur l'application elle-même. Faire toute cette logique sur la requête nécessitera beaucoup de jointures internes et je ne suis pas sûr à 100% de pouvoir faire tout cela à l'intérieur de la procédure.

J'utilise SQL Server 20

59
Victor Rodrigues

Lors de l'écriture d'une requête à l'aide de l'opérateur IN, plusieurs considérations peuvent affecter les performances.

Premièrement, les clauses IN sont généralement réécrites en interne par la plupart des bases de données pour utiliser le connecteur logique OR. So col IN ('a','b','c') est réécrit en: (COL = 'a') OR (COL = 'b') or (COL = 'c'). Le plan d'exécution pour les deux requêtes sera probablement équivalent en supposant que vous avez un index sur col.

Deuxièmement, lorsque vous utilisez IN ou OR avec un nombre variable d'arguments, vous obligez la base de données à ré-analyser la requête et à reconstruire une exécution planifier chaque fois que les arguments changent. La construction du plan d'exécution pour une requête peut être une étape coûteuse. La plupart des bases de données mettent en cache les plans d'exécution pour les requêtes qu'elles exécutent en utilisant le texte de la requête EXACT comme clé. Si vous exécutez une requête similaire mais avec des valeurs d'argument différentes dans le prédicat - vous ferez probablement passer la quantité de temps considérable à la base de données à analyser et à construire des plans d'exécution. C'est pourquoi les variables de liaison sont fortement recommandées comme un moyen d'assurer des performances optimales de requête.

Troisièmement, de nombreuses bases de données ont une limite à la complexité des requêtes qu'elles peuvent exécuter - l'une de ces limites est le nombre de connecteurs logiques qui peuvent être inclus dans le prédicat. Dans votre cas, il est peu probable que quelques dizaines de valeurs atteignent la limite intégrée de la base de données, mais si vous prévoyez de transmettre des centaines ou des milliers de valeurs à une clause IN - cela peut certainement arriver. Dans ce cas, la base de données annule simplement la demande de requête.

Quatrièmement, les requêtes qui incluent IN et OR dans le prédicat ne peuvent pas toujours être réécrites de manière optimale dans un environnement parallèle. Il existe divers cas où l'optimisation du serveur parallèle n'est pas appliquée - MSDN a une introduction décente à l'optimisation des requêtes pour le parallélisme. Généralement cependant, les requêtes qui utilisent l'opérateur UNION ALL sont trivialement parallélisables dans la plupart des bases de données - et sont préférées à connecteurs logiques (comme OR et IN) lorsque cela est possible.

118
LBushkin

Si vous avez un bon index sur FieldS, utiliser cet IN est tout à fait correct.

Je viens de tester et SQL 2000 effectue une analyse d'index en cluster lors de l'utilisation de l'IN.

5
tekBlues

Vous pouvez essayer de créer une table temporaire, y insérer vos valeurs et utiliser la table à la place dans le prédicat IN.

AFAIK, SQL Server 2000 Ne peut pas construire une table de hachage de l'ensemble de constantes, ce qui prive l'optimiseur de la possibilité d'utiliser un HASH SEMI JOIN.

Cela n'aidera que si vous n'avez pas d'index sur FieldW (que vous devriez avoir).

Vous pouvez également essayer d'inclure vos colonnes FieldX et FieldY dans l'index:

CREATE INDEX ix_a_wxy ON a (FieldW, FieldX, FieldY)

afin que la requête ne puisse être servie qu'en utilisant l'index.

SQL Server 2000 N'a pas l'option INCLUDE pour CREATE INDEX Et cela peut dégrader légèrement les performances de DML mais améliorer les performances de la requête.

Mise à jour:

D'après votre plan d'exécution, je vois que vous avez besoin d'un index composite sur (SettingsID, SectionID)

SQL Server 2000 Peut en effet construire une table de hachage à partir d'une liste constante (et le fait), mais Hash Semi Join Sera probablement moins efficace qu'un Nested Loop Pour une requête de requête.

Et juste une remarque: si vous avez besoin de connaître le nombre de lignes satisfaisant la condition WHERE, n'utilisez pas COUNT(column), utilisez COUNT(*) à la place.

Une COUNT(column) ne compte pas les lignes pour lesquelles la valeur column est NULL.

Cela signifie que, premièrement, vous pouvez obtenir les résultats auxquels vous ne vous attendiez pas, et, deuxièmement, l'optimiseur devra faire un supplément Key Lookup/Bookmark Lookup Si votre colonne n'est pas couverte par un index qui remplit la condition WHERE.

Puisque ThreadId semble être un CLUSTERED PRIMARY KEY, Tout va bien pour cette requête, mais essayez de l'éviter en général.

5
Quassnoi

Il existe de meilleures façons de le coder, mais je doute que ce soit la cause de vos délais d'attente, surtout s'il ne s'agit que d'un SELECT. Vous devriez être en mesure de le déterminer en regardant les traces de votre requête. Mais recoder cela serait une optimisation en devinant, et une supposition improbable à cela.

Commençons par un plan de requête pour la requête qui arrive à expiration. Savez-vous avec certitude de quelle requête il s'agit?

3
dkretz

Selon votre distribution de données, des prédicats supplémentaires dans votre clause WHERE peuvent améliorer les performances. Par exemple, si l'ensemble des identifiants est petit par rapport au nombre total dans le tableau et que vous savez que les identifiants sont relativement proches les uns des autres (peut-être seront-ils généralement des ajouts récents, et donc regroupés à l'extrémité supérieure de la plage), vous pouvez essayer d'inclure le prédicat "AND FieldW BETWEEN 109 AND 891" (après avoir déterminé l'ID min & max dans votre ensemble dans le code C #). Il se peut que l'exécution d'une analyse de plage sur ces colonnes (si elles sont indexées) fonctionne plus rapidement que ce qui est actuellement utilisé.

3
Steve Broberg

IN est exactement la même chose que d'écrire une grande liste de blocs opératoires. Et OR rend souvent les requêtes INSARGABLES, donc vos index peuvent être ignorés et le plan va pour une analyse complète.

2
Remus Rusanu

En règle générale, la clause IN nuit aux performances, mais ce qui est "mauvais" dépend de l'application, des données, de la taille de la base de données, etc. Vous devez tester votre propre application pour voir ce qui est le mieux.

1
Bryan Migliorisi

Fondamentalement, ce que fait la clause where est "FieldW = 108 OR FieldW = 109 OR FieldW = 113 ...". Parfois, vous pouvez obtenir de meilleures performances en faisant plusieurs sélections et les combiner avec l'union. Par exemple:

SELECT FieldX, FieldY FROM A WHERE FieldW = 108
UNION ALL
SELECT FieldX, FieldY FROM A WHERE FieldW = 109

Mais bien sûr, cela n'est pas pratique lorsque vous comparez à tant de valeurs.

Une autre option pourrait être d'insérer ces valeurs dans une table temporaire, puis de joindre la table A à cette table temporaire.

1
Tommi

la taille de votre table déterminera la vitesse lors de l'utilisation de cette instruction. Si ce n'est pas une très grande table ... cette déclaration n'affecte pas vos performances.

1
Eric

J'utilise généralement un type de table défini par l'utilisateur pour des requêtes comme celle-ci.

CREATE TYPE [dbo].[udt_int] AS TABLE (
    [id] [int] NOT NULL
)

En utilisant une variable de table et en la remplissant de lignes pour chacun de vos nombres, vous pouvez faire:

SELECT 
    FieldX, 
    FieldY
FROM A
INNER JOIN @myIds B ON
    A.FieldW = B.id
1
Donald.Record

Voici votre réponse ...

http://www.4guysfromrolla.com/webtech/031004-1.shtml

Fondamentalement, vous souhaitez créer une fonction qui fractionnera une chaîne et remplira une table temporaire avec le contenu divisé. Ensuite, vous pouvez rejoindre cette table temporaire et manipuler vos données. Ce qui précède explique assez bien les choses. J'utilise beaucoup cette technique.

Dans votre cas spécifique, utilisez une jointure à la table temporaire au lieu d'une clause in, beaucoup plus rapidement.

1
infocyde

Vous pourriez essayer quelque chose comme:

select a.FieldX, a.FieldY
from (
    select FieldW = 108 union
    select FieldW = 109 union
    select FieldW = 113 union
    ...
    select FieldW = 891
) _a
join A a on a.FieldW = _a.FieldW

Cela peut convenir à votre situation, par exemple lorsque vous souhaitez générer dynamiquement une seule instruction SQL. Sur ma machine (SQL Server 2008 Express), en testant avec un petit nombre (5) de valeurs FieldW et un grand nombre (100 000) de lignes dans A, cela utilise une recherche d'index sur A avec une jointure de boucles imbriquées entre A et _a, c'est probablement ce que vous recherchez.

0
yfeldblum

La performance ne peut être jugée que dans le contexte de ce que vous essayez de faire. Dans ce cas, vous demandez la récupération d'environ 70 lignes (en supposant qu'il s'agit de valeurs uniques), vous pouvez donc vous attendre à quelque chose comme 70 fois la durée de récupération d'une seule valeur. Cela peut être moins dû à la mise en cache ou bien sûr.

Cependant, l'optimiseur de requêtes peut avoir besoin ou choisir d'effectuer une analyse complète de la table afin de récupérer les valeurs, auquel cas performace sera peu différent de la récupération d'une seule valeur via le même plan d'accès.

0
David Aldridge

Si vous pouvez utiliser autre chose qu'IN: faites-le (j'utilisais IN dans certains cas pas vraiment la bonne façon: je peux facilement remplacer par exist et c'est plus rapide)

Dans votre cas: ça ne semble pas si mal.

0