Cerca predicato che non utilizza tutte le colonne disponibili


8

Ho uno strano problema di compilazione di query che è difficile da riprodurre. Succede solo sotto carico elevato e non può essere facilmente ripetuto.

  • C'è una tabella T con colonne A, B, C, D.
  • Esiste un indice cluster non univoco su T (A, B, C, D).
  • Esiste una query SELEZIONA * DA T DOVE A = @ P1 AND B = @ P2 AND (C = @ P3 OR C = @ P4) E D = @ P5. La condizione di ricerca è su tutte le colonne dell'indice cluster, la terza colonna ha un OR.

Il problema è che il piano di query per questa query ha Cerca predicato solo su A e B! Il predicato su C e D è un predicato ordinario, quindi ciò significa che l'albero di ricerca sulle colonne C e D non viene utilizzato.

I tipi di dati per tutti i parametri corrispondono ai tipi di dati delle colonne.

Qualcuno potrebbe fornire qualche suggerimento sul perché ciò potrebbe accadere? La versione SQL è 2008 R2 (SP1) - 10.50.2789.0 (X64)


Hai mai effettivamente un piano per una query con parametri che esegue una ricerca di uguaglianza su tutte e 4 le colonne? Se è così stai usando OPTION (RECOMPILE)?
Martin Smith,

Risposte:


8

Per una query con parametri Non può fare solo due ricerche

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

e

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

Perché se @P3 = @P4ciò restituisse erroneamente righe duplicate. Quindi avrebbe bisogno di un operatore che rimuova i duplicati da questi per primi.

Da un test rapido questo fine sembra dipendere dalla dimensione della tabella, indipendentemente dal fatto che tu lo ottenga o meno. Nel test di seguito 245/ 246righe è il punto di interruzione tra i piani (questo era anche il punto di interruzione tra l'indice che si adattava tutto su una pagina e diventava 2 pagine foglia e una pagina radice).

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 Pagine / 245 righe

Questo piano ha una ricerca A=1 AND B=2con un predicato residuo su(C=@C1 OR C=@C2) AND D=5

Piano 1

2 pagine a foglia / 246 righe

Piano 2

Nel secondo piano gli operatori extra sono responsabili della rimozione di eventuali duplicati dal @C1,@C2primo prima di eseguire le ricerche.

La ricerca nel secondo piano è in realtà una ricerca della distanza tra A=1 AND B=2 AND C > Expr1010e A=1 AND B=2 AND C < Expr1011con un predicato residuo su D=5. Non è ancora una ricerca di uguaglianza su tutte e 4 le colonne. Ulteriori informazioni sugli operatori di piani aggiuntivi sono disponibili qui .

L'aggiunta OPTION (RECOMPILE)consente di ispezionare i valori dei parametri per i duplicati in fase di compilazione e produce un piano con due ricerche di uguaglianza.

Potresti farlo anche con

;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

Piano 3

Ma in realtà in questo caso di test sarebbe probabilmente controproducente poiché avere due ricerche nell'indice a pagina singola anziché una aumenta l'IO logico.


1
Ho fatto alcuni test su questa domanda ieri sera prima di aggiungere il tuo primo commento. Sono arrivato al punto di vedere il comportamento ma non ho capito cosa lo stava causando (@ P3 = @ P4), quindi +1 per quello (già fatto). Penso che ti select .. union select ...darebbe anche due ricerche oltre al passaggio aggiuntivo di rimozione dei duplicati dal risultato.
Mikael Eriksson,

1
@MikaelEriksson - Ma 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=5potrebbe rimuovere in modo errato i duplicati che dovrebbero essere restituiti. Nei miei dati di esempio in cui ho popolato pigramente tutti con lo stesso valore, non restituirebbe 1 riga no256
Martin Smith,

2
Ah sì. E OP ha anche specificato che la chiave non è univoca. Per fortuna non ho provato a rispondere a questo :).
Mikael Eriksson il

4

Totalmente d'accordo con l'analisi di Martin. Questa query non può produrre una ricerca su tutte e 4 le colonne a causa del predicato OR, a meno che (forse) con OPTION (RECOMPILE). Immagino che si tratti di una domanda ago nel pagliaio, probabilmente non vorrai il sovraccarico extra.

Cosa ne pensi di questo:

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.

Non ho provato questo, ma l'altra parte dovrebbe dare 2 ricerche su tutti e 4 i valori e un'unione economica attraverso la concatenazione. Nel caso in cui entrambe le ricerche finiscano nella stessa pagina, non penso che una lettura logica in più della pagina richiederebbe molto tempo. Tuttavia, a seconda dei dati, le due ricerche potrebbero benissimo trovarsi su pagine diverse.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.