2009-07-09 4 views
6

ho una abbastanza semplice query:SQL Server non utilizzerà il mio indice

SELECT 
    col1, 
    col2… 
FROM 
    dbo.My_Table 
WHERE 
    col1 = @col1 AND 
    col2 = @col2 AND 
    col3 <= @col3 

Si stava eseguendo orribilmente, così ho aggiunto un indice su col1, col2, col3 (int, bit e datetime). Quando ho controllato il piano di query, ignoravo il mio indice. Ho provato a riordinare le colonne nell'indice in ogni configurazione possibile e ha sempre ignorato l'indice. Quando eseguo la query esegue una scansione dell'indice cluster (la dimensione della tabella è compresa tra 700 K e 800 K righe) e richiede 10-12 secondi. Quando lo costringo a usare il mio indice, ritorna istantaneamente. Ho fatto attenzione a svuotare la cache e i buffer tra i test.

Altre cose che ho provato:

UPDATE STATISTICS dbo.My_Table 

CREATE STATISTICS tmp_stats ON dbo.My_Table (col1, col2, col3) WITH FULLSCAN 

Mi manca qualcosa qui? Odio mettere un suggerimento di indice in una stored procedure, ma SQL Server non sembra proprio avere un indizio su questo. Qualcuno sa qualsiasi altra cosa che potrebbe impedire a SQL Server di riconoscere che l'utilizzo dell'indice è una buona idea?

EDIT: Una delle colonne da restituire è una colonna TEXT, in modo da utilizzare un indice di copertura o un INCLUDE non funzionerà :(

+1

Hai inserito i puntini di sospensione nell'elenco delle colonne, quali altre colonne stai selezionando? E se fosse solo col1, col2 e col3? –

+0

Chris, buon punto ma "Quando impongo di usare il mio indice, ritorna istantaneamente" lo copre. –

+0

I puntini di sospensione sono perché la query restituisce tutte le colonne nella tabella. Suppongo che avrei potuto inserire SELECT * –

risposta

13

Hai 800k file indicizzati da col1, col2, col3. Col2 è un po ', quindi la sua selettività è del 50%. Col3 è un controllo su un intervallo (< =), quindi la selettività sarà approssimativamente pari a circa il 50%. Che lascia col1. La query è compilata per il piano generico e parametrizzato, quindi deve tenere conto del caso generale. Se hai 10 valori distinti di col1, il tuo indice restituirà approssimativamente 800k/10 * 25% che è circa ~ 20k chiavi per la ricerca nell'indice cluster per recuperare la parte '...'. Se hai 10k distinti valori di col1, l'indice restituirà solo 20 chiavi per cercare. Come puoi vedere, ciò che conta non è il modo in cui costruisci il tuo indice in questo caso, ma i dati effettivi. In base alla selettività di col1, l'ottimizzatore sceglierà un piano basato su una scansione dell'indice cluster (migliore delle ricerche con chiave 20k, ogni ricerca al costo di almeno letture di pagine 3-5) o una basata sul non- indice cluster (se col1 è abbastanza selettivo). Nella vita reale anche la distribuzione di col1 gioca un ruolo, ma entrare in quella complicherebbe troppo la spiegazione.

È possibile venire con il senno di poi e dichiarare che il piano è sbagliato, ma il piano è la migliore stima dei costi basata sui dati disponibili al momento della compilazione. Puoi influenzarlo con suggerimenti (suggerimento indice come suggerisci, o ottimizzare per suggerimenti come suggerisce Quassnoi) ma poi la tua query potrebbe essere migliore per il tuo set di test, e molto peggio per un diverso set di dati, ad esempio nel caso in cui @ col1 = <the value that matches 500k records>. È anche possibile creare la copertura dell'indice, eliminando così il '...' nell'elenco di proiezione che richiede la ricerca dell'indice cluster necessario, nel qual caso l'indice non cluster è sempre una corrispondenza di costo migliore rispetto alla scansione in cluster.

Kimberley Tripp ha un articolo del blog che copre questo argomento, lei lo chiama il 'index tipping point' che spiega come mai un indice candidato apparentemente perfetto viene ignorato: un indice non cluster che non copre la lista di proiezione ed ha poveri la selettività sarà considerata più costosa di una scansione in cluster.

+0

Grazie per le informazioni e i suggerimenti. Mi dà alcune idee da esaminare. –

1

L'ordine dell'indice è importante per questa ricerca:

CREATE INDEX MyIndex ON MyTable (col3 DESC, col2 ASC, col1 ASC) 

non è tanto l'ASC/DESC come quella quando il server SQL va a corrispondere quella clausola in cui si può abbinare al primo col3 e camminare l'indice lungo quel valore.

+1

Per questa query, l'ordine dell'indice dovrebbe essere proprio come @Tom H. lo ha creato. – Quassnoi

+0

Ho provato diversi possibili ordini per le colonne. Tutti hanno dato lo stesso risultato. –

2

SQL Server ottimizzatore non è buono in o query di ottimizzazione che utilizzano variabili.

Se sei sicuro di trarre sempre vantaggio dall'utilizzo dell'indice, metti un suggerimento.

Se si inseriscono i valori letterali nella query anziché nelle variabili, verranno selezionate le statistiche corrette e verrà utilizzato l'indice.

Si può anche provare a mettere un pizzico di luce in più:

OPTION (OPTIMIZE FOR (@col1 = 1, @col2 = 0, @col3 = '2009-07-09')) 

, che calcolerà il miglior piano di esecuzione per questi valori delle variabili, utilizzando statistiche, e non si attacchi ai utilizzando l'indice non importa che cosa.

+0

Se eseguo la query all'esterno dell'SP, con i valori della colonna codificati, utilizza ancora una scansione dell'indice cluster :( –

+0

@Tom: potresti inserire la definizione esatta della tabella? – Quassnoi

1

Hai provato a eliminare il bit dall'indice?

create index ix1 on My_Table(Col3, Col1) INCLUDE(Col2) 
-- include other columns from the select list if needed 

Inoltre, hai omesso il resto delle colonne dall'elenco di selezione. Potresti considerare di includere quelli se non ci sono molti nell'indice o come istruzione INCLUDE per creare un indice di copertura per la query.

1

Prova mascherare i parametri per evitare paramter sniffing:

CREATE PROCEDURE MyProc AS 
    @Col1 INT 
    -- etc... 
AS 
    DECLARE @MaskedCol1 INT 
    SET @MaskedCol1 = @Col1 
    -- etc... 

    SELECT 
     col1, 
     col2… 
    FROM 
     dbo.My_Table 
    WHERE 
     col1 = @MaskecCol1 AND 
     -- etc... 

sembra stupido ma ho visto SQL server fare alcune cose strane a causa del parametro di sniffing.

+0

Grazie per il suggerimento.Posso eseguire SELECT all'esterno del SP e vedo ancora la stessa situazione. –

1

Scommetto che SQL Server ritiene che il prezzo per ottenere il resto delle colonne (designato da ... nel tuo esempio) dall'indice cluster superi il vantaggio dell'indice in modo da eseguire solo la scansione della chiave in cluster. Se è così, vedi se puoi renderlo un indice di copertura.

Oppure utilizza un altro indice?

+0

Sta usando la chiave cluster se non forzare l'uso dell'indice. L'elenco delle colonne include tutte le colonne nella tabella. Mentre potrei mettere un grande indice di copertura su questo, sarei effettivamente in grado di duplicare il tavolo. Avrò bisogno di esaminare le frequenze INSERT/UPDATE/DELETE per vedere se il costo è giustificato. –

0

Le colonne sono annullabili? A volte il server Sql pensa di dover eseguire la scansione della tabella per trovare valori NULL.

Prova ad aggiungere "e col1 non è null" alla query, mgiht rendere sqlserver utilizzare l'indice wtihout hint.

Inoltre, verificare se le statistiche sono davvero fino ad oggi:

SELECT 
    object_name = Object_Name(ind.object_id), 
    IndexName = ind.name, 
    StatisticsDate = STATS_DATE(ind.object_id, ind.index_id) 
FROM SYS.INDEXES ind 
order by STATS_DATE(ind.object_id, ind.index_id) desc 
+0

Diversamente da Oracle, SQL Server indicizza NULL altrettanto bene e un indice copre sempre tutte le righe. – Quassnoi

+0

La mia teoria era che a volte le statistiche mostrano che ci sono molte righe con valori NULL. Quindi il server Sql ritorna a una scansione della tabella per coprire il caso "c1 è nullo". – Andomar

+0

@Andomar: questa query non può mai includere righe per le quali 'COL1' è' NULL' – Quassnoi

0

Se SELECT restituisce colonne che non sono nell'indice SQL, trovo che sia più efficiente eseguire la scansione dell'indice cluster invece di dover eseguire una ricerca chiave per trovare gli altri valori che si stanno richiedendo.

Se si dispone di una colonna TEXT, provare a passare il tipo di dati a VARCHAR (MAX), quindi includere i valori nell'indice non cluster.