J'ai besoin d'optimiser une SELECT
instruction, mais SQL Server effectue toujours une analyse d'index au lieu d'une recherche. C'est la requête qui, bien sûr, se trouve dans une procédure stockée:
CREATE PROCEDURE dbo.something
@Status INT = NULL,
@IsUserGotAnActiveDirectoryUser BIT = NULL
AS
SELECT [IdNumber], [Code], [Status], [Sex],
[FirstName], [LastName], [Profession],
[BirthDate], [HireDate], [ActiveDirectoryUser]
FROM Employee
WHERE (@Status IS NULL OR [Status] = @Status)
AND
(
@IsUserGotAnActiveDirectoryUser IS NULL
OR
(
@IsUserGotAnActiveDirectoryUser IS NOT NULL AND
(
@IsUserGotAnActiveDirectoryUser = 1 AND ActiveDirectoryUser <> ''
)
OR
(
@IsUserGotAnActiveDirectoryUser = 0 AND ActiveDirectoryUser = ''
)
)
)
Et voici l'indice:
CREATE INDEX not_relevent ON dbo.Employee
(
[Status] DESC,
[ActiveDirectoryUser] ASC
)
INCLUDE (...all the other columns in the table...);
Le plan:
Pourquoi SQL Server a-t-il choisi une analyse? Comment puis-je le réparer?
Définitions des colonnes:
[Status] int NOT NULL
[ActiveDirectoryUser] VARCHAR(50) NOT NULL
Les paramètres d'état peuvent être:
NULL: all status,
1: Status= 1 (Active employees)
2: Status = 2 (Inactive employees)
IsUserGotAnActiveDirectoryUser peut être:
NULL: All employees
0: ActiveDirectoryUser is empty for that employee
1: ActiveDirectoryUser got a valid value (not null and not empty)
@Status
?Status DESC
? Pour combien de valeurs existe-t-ilStatus
, quelles sont-elles (si le nombre est petit) et chaque valeur est-elle représentée à peu près également? Montrez-nous la sortie deSELECT TOP (20) [Status], c = COUNT(*) FROM dbo.Employee GROUP BY [Status] ORDER BY c DESC;
Réponses:
Je ne pense pas que l'analyse soit causée par une recherche d'une chaîne vide (et bien que vous puissiez ajouter un index filtré pour ce cas, cela n'aidera que des variations très spécifiques de la requête). Vous êtes probablement victime d'un reniflage de paramètres et d'un plan unique non optimisé pour toutes les diverses combinaisons de paramètres (et valeurs de paramètres) que vous fournirez à cette requête.
J'appelle cela la procédure "évier de cuisine" , car vous vous attendez à ce qu'une requête fournisse tout, y compris l'évier de cuisine.
J'ai une vidéo sur ma solution ici , mais essentiellement, la meilleure expérience que j'ai pour de telles requêtes est de:
OPTION (RECOMPILE)
- cela empêche des valeurs de paramètres spécifiques de forcer le mauvais type de plan, particulièrement utile lorsque vous avez un biais de données, de mauvaises statistiques ou lorsque la première exécution d'une instruction utilise une valeur atypique qui conduira à un plan différent de celui plus tard et plus fréquent exécutions.optimize for ad hoc workloads
- cela empêche les variations de requête qui ne sont utilisées qu'une seule fois de polluer le cache de votre plan.Activez l'optimisation pour les charges de travail ad hoc:
Modifiez votre procédure:
Une fois que vous avez une charge de travail basée sur cet ensemble de requêtes que vous pouvez surveiller, vous pouvez analyser les exécutions et voir celles qui bénéficieraient le plus d'index supplémentaires ou différents - vous pouvez le faire sous divers angles, du simple "quelle combinaison de les paramètres sont fournis le plus souvent? " à "quelles requêtes individuelles ont les durées d'exécution les plus longues?" Nous ne pouvons pas répondre à ces questions uniquement en fonction de votre code, nous pouvons seulement suggérer que tout index ne sera utile que pour un sous-ensemble de toutes les combinaisons de paramètres possibles que vous essayez de prendre en charge. Par exemple, si
@Status
est NULL, alors aucune recherche contre cet index non cluster n'est possible. Donc, pour les cas où les utilisateurs ne se soucient pas du statut, vous allez obtenir une analyse, sauf si vous avez un index qui répond aux autres clauses (mais un tel index ne sera pas utile non plus, compte tenu de votre logique de requête actuelle - soit une chaîne vide soit une chaîne non vide n'est pas exactement sélective).Dans ce cas, en fonction de l'ensemble des
Status
valeurs possibles et de la répartition de ces valeurs, laOPTION (RECOMPILE)
peut ne pas être nécessaire. Mais si vous avez des valeurs qui produiront 100 lignes et des valeurs qui produiront des centaines de milliers, vous voudrez peut-être qu'elles soient là (même au coût du processeur, qui devrait être marginal compte tenu de la complexité de cette requête), afin que vous puissiez get cherche dans autant de cas que possible. Si la plage de valeurs est suffisamment finie, vous pouvez même faire quelque chose de délicat avec le SQL dynamique, où vous dites "J'ai cette valeur très sélective pour@Status
, donc quand cette valeur spécifique est passée, apportez cette légère modification au texte de la requête afin que cela est considéré comme une requête différente et optimisé pour cette valeur de paramètre. "la source
Avertissement : Certains éléments de cette réponse peuvent faire tressaillir DBA. Je l'aborde du point de vue des performances pures - comment obtenir des recherches d'index lorsque vous obtenez toujours des analyses d'index.
Avec cela à l'écart, voici.
Votre requête est ce que l'on appelle une "requête d'évier de cuisine" - une requête unique destinée à répondre à une gamme de conditions de recherche possibles. Si l'utilisateur définit
@status
une valeur, vous souhaitez filtrer sur cet état. Si@status
c'est le casNULL
, renvoyez tous les statuts, etc.Cela pose des problèmes d'indexation, mais ils ne sont pas liés à la sargabilité, car toutes vos conditions de recherche sont "égales" aux critères.
C'est discutable:
Ce n'est pas discutable car SQL Server doit évaluer
ISNULL([status], 0)
pour chaque ligne au lieu de rechercher une seule valeur dans l'index:J'ai recréé le problème de l'évier de cuisine sous une forme plus simple:
Si vous essayez ce qui suit, vous obtiendrez une analyse d'index, même si A est la première colonne de l'index:
Cependant, cela produit une recherche d'index:
Tant que vous utilisez une quantité gérable de paramètres (deux dans votre cas), vous pourriez probablement juste
UNION
un tas de requêtes de recherche - essentiellement toutes les permutations des critères de recherche. Si vous avez trois critères, cela semblera compliqué, avec quatre, ce sera complètement ingérable. Tu as été prévenu.Pour que le troisième de ces quatre utilise une recherche d'index, vous aurez cependant besoin d'un deuxième index
(B, A)
. Voici à quoi pourrait ressembler votre requête avec ces modifications (y compris ma refactorisation de la requête pour la rendre plus lisible).... en plus, vous aurez besoin d'un index supplémentaire
Employee
avec les deux colonnes d'index inversées.Pour être complet, je dois mentionner que cela
x=@x
signifie implicitement quex
cela ne peut pas êtreNULL
parce queNULL
n'est jamais égal àNULL
. Cela simplifie un peu la requête.Et, oui, la réponse SQL dynamique d'Aaron Bertrand est un meilleur choix dans la plupart des cas (c'est-à-dire chaque fois que vous pouvez vivre avec les recompilations).
la source
Votre question de base semble être "Pourquoi" et je pense que vous pourriez trouver la réponse à propos de la minute 55 ou plus de cette Grande présentation d'Adam Machanic à TechEd il y a quelques années.
Je mentionne les 5 minutes à la minute 55 mais toute la présentation en vaut la peine. Si vous regardez le plan de requête pour votre requête, je suis sûr que vous trouverez qu'il a des prédicats résiduels pour la recherche. Fondamentalement, SQL ne peut pas "voir" toutes les parties de l'index car certaines d'entre elles sont masquées par les inégalités et d'autres conditions. Le résultat est un balayage d'index pour un super ensemble basé sur le prédicat. Ce résultat est mis en file d'attente, puis analysé à nouveau à l'aide du prédicat résiduel.
Vérifiez les propriétés de l'opérateur de numérisation (F4) et voyez si vous avez à la fois "Seek Predicate" et "Predicate" dans la liste des propriétés.
Comme d'autres l'ont indiqué, la requête est difficile à indexer telle quelle. J'ai travaillé sur de nombreux projets similaires récemment et chacun a nécessité une solution différente. :(
la source
Avant de nous demander si la recherche d'index est préférée à l'analyse d'index, une règle d'or consiste à vérifier le nombre de lignes renvoyées par rapport au nombre total de lignes de la table sous-jacente. Par exemple, si vous vous attendez à ce que votre requête renvoie 10 lignes sur 1 million de lignes, la recherche d'index est probablement hautement préférée à l'analyse d'index. Cependant, si quelques milliers de lignes (ou plus) doivent être renvoyées à partir de la requête, la recherche d'index n'est PAS nécessairement préférable.
Votre requête n'est pas complexe, donc si vous pouvez publier un plan d'exécution, nous pouvons avoir de meilleures idées pour vous aider.
la source
c'est juste l'original formaté
ceci est la révision - pas sûr à 100% mais (peut-être) essayer
même un OU cela va probablement être un problème
qui se briserait sur ActiveDirectoryUser null
la source