Recherche de prédicat n'utilisant pas toutes les colonnes disponibles

8

J'ai un étrange problème de compilation de requêtes qui est difficile à reproduire. Cela ne se produit que sous une charge élevée et ne peut pas être facilement répété.

  • Il y a un tableau T avec les colonnes A, B, C, D.
  • Il existe un index cluster non unique sur T (A, B, C, D).
  • Il y a une requête SELECT * FROM T WHERE A = @ P1 AND B = @ P2 AND (C = @ P3 OR C = @ P4) AND D = @ P5. La condition de recherche est sur toutes les colonnes de l'index clusterisé, la 3ème colonne a un OU.

Le problème est que le plan de requête pour cette requête a un prédicat de recherche uniquement sur A et B! Le prédicat sur C et D est un prédicat ordinaire, ce qui signifie que l'arborescence de recherche sur les colonnes C et D n'est pas utilisée.

Les types de données pour tous les paramètres correspondent aux types de données de colonne.

Quelqu'un pourrait-il fournir des indices sur la raison pour laquelle cela pourrait se produire? La version SQL est 2008 R2 (SP1) - 10.50.2789.0 (X64)


la source
Avez-vous déjà obtenu un plan pour une requête paramétrée qui effectue une recherche d'égalité sur les 4 colonnes? Si oui, utilisez-vous OPTION (RECOMPILE)?
Martin Smith

Réponses:

8

Pour une requête paramétrée Il ne peut pas simplement faire deux recherches sur

WHERE A=@P1 AND B=@P2 AND C=@P3 AND D=@P5 

et

WHERE A=@P1 AND B=@P2 AND C=@P4 AND D=@P5 

Parce @P3 = @P4que si cela ramènerait incorrectement des lignes en double. Il faudrait donc un opérateur qui supprime les doublons de ceux-ci en premier.

D'après un test rapide à cette fin, il semble dépendre de la taille de la table, que vous l'obteniez ou non. Dans le test ci 245- dessous / 246rangées se trouve le point de coupure entre les plans (c'était aussi le point de coupure entre l'index s'adaptant le tout sur une seule page et devenant 2 pages feuilles et une page racine).

CREATE TABLE T(A INT,B INT,C INT,D INT)

INSERT INTO T
SELECT TOP (245) 1,2,3,5
FROM master..spt_values v1

CREATE CLUSTERED INDEX IX ON T(A, B, C, D)

SELECT index_level,page_count, record_count
FROM sys.dm_db_index_physical_stats(db_id(),object_id('T'),1,NULL, 'DETAILED')

DECLARE @C1 INT = 3,
        @C2 INT = 4

 SELECT * FROM T WHERE A=1 AND B=2 AND (C=@C1 OR C=@C2) AND D=5

 DROP TABLE T

1 pages / 245 lignes

Ce plan a une recherche sur A=1 AND B=2avec un prédicat résiduel sur(C=@C1 OR C=@C2) AND D=5

Plan 1

Pages 2 feuilles / 246 lignes

Plan 2

Dans le second plan, les opérateurs supplémentaires sont responsables de la suppression des doublons @C1,@C2avant d'effectuer les recherches.

La recherche dans le deuxième plan est en fait une recherche de plage entre A=1 AND B=2 AND C > Expr1010et A=1 AND B=2 AND C < Expr1011avec un prédicat résiduel activé D=5. Ce n'est toujours pas une recherche d'égalité sur les 4 colonnes. Pour plus d'informations sur les opérateurs de plans supplémentaires, cliquez ici .

L'ajout OPTION (RECOMPILE)lui permet d'inspecter les valeurs des paramètres pour les doublons au moment de la compilation et produit un plan avec deux recherches d'égalité.

Vous pouvez également y parvenir avec

;WITH CTE
     AS (SELECT DISTINCT ( C )
         FROM   (VALUES (@C1),
                        (@C2)) V(C))
SELECT CA.*
FROM   CTE
       CROSS APPLY (SELECT *
                    FROM   T
                    WHERE A=1 AND B=2 AND D=5  AND C = CTE.C) CA

Plan 3

Mais en réalité, dans ce cas de test, il serait probablement contre-productif, car avoir deux recherches dans l'index d'une seule page plutôt qu'une seule augmente le nombre d'entrées / sorties logiques.

Martin Smith
la source
1
Avez-vous fait des tests sur cette question hier soir avant d'ajouter votre premier commentaire. Je suis venu jusqu'à voir le comportement mais je n'ai pas compris ce qui le provoquait (@ P3 = @ P4) donc +1 pour cela (déjà fait). Je pense que select .. union select ...cela vous donnerait également deux recherches plus l'étape supplémentaire de suppression des doublons du résultat.
Mikael Eriksson
1
@MikaelEriksson - Mais SELECT * FROM T WHERE A=1 AND B=2 AND C=@C1 AND D=5 UNION SELECT * FROM T WHERE A=1 AND B=2 AND C=@C2 AND D=5pourrait supprimer de manière incorrecte les doublons qui devraient être retournés. Dans mes données d'exemple où j'ai rempli paresseusement tous avec la même valeur, cela retournerait 1 ligne non256
Martin Smith
2
Ah oui. Et OP a même spécifié que la clé n'est pas unique. Lucke moi je n'ai pas essayé de répondre à celui-ci :).
Mikael Eriksson
4

Tout à fait d'accord avec l'analyse de Martin. Cette requête ne peut pas produire de recherche sur les 4 colonnes en raison du prédicat OR, sauf (peut-être) avec OPTION (RECOMPILE). Je suppose que c'est une requête d'aiguille dans une botte de foin, vous ne voulez probablement pas les frais généraux supplémentaires.

Que dis-tu de ça:

IF @P3=@P4
    SELECT * FROM T WHERE A=@P1 AND B=@P2 AND C=@P3 AND D=@P5.
ELSE
    SELECT * FROM T WHERE A=@P1 AND B=@P2 AND C=@P3 AND D=@P5.
    UNION ALL
    SELECT * FROM T WHERE A=@P1 AND B=@P2 AND C=@P4 AND D=@P5.

Je n'ai pas testé cela, mais l'autre partie devrait donner 2 recherches sur les 4 valeurs et une union bon marché par concaténation. Dans le cas où les deux recherches se retrouveraient sur la même page, je ne pense pas qu'une lecture logique supplémentaire ajouterait beaucoup de temps. Cependant, selon vos données, les deux recherches peuvent très bien se trouver sur des pages différentes.

Vlad G.
la source