Perché SQL Server dovrebbe ignorare un indice?


16

Ho una tabella, CustPassMastercon 16 colonne, una delle quali è CustNum varchar(8), e ho creato un indice IX_dbo_CustPassMaster_CustNum. Quando eseguo la mia SELECTdichiarazione:

SELECT * FROM dbo.CustPassMaster WHERE CustNum = '12345678'

Ignora completamente l'indice. Questo mi confonde perché ho un'altra tabella CustDataMastercon molte più colonne (55), una delle quali è CustNum varchar(8). Ho creato un indice su questa colonna ( IX_dbo_CustDataMaster_CustNum) in questa tabella e uso praticamente la stessa query:

SELECT * FROM dbo.CustDataMaster WHERE CustNum = '12345678'

E utilizza l'indice che ho creato.

C'è qualche ragionamento specifico dietro questo? Perché dovrebbe usare l'indice di CustDataMaster, ma non quello di CustPassMaster? È dovuto al numero di colonne basso?

La prima query restituisce 66 righe. Per la seconda, viene restituita 1 riga.

Inoltre, nota aggiuntiva: CustPassMasterha 4991 record e CustDataMaster5376 record. Potrebbe essere questo il ragionamento alla base dell'ignorare l'indice? CustPassMasterha anche record duplicati che hanno gli stessi CustNumvalori. Questo è un altro fattore?

Sto basando questa affermazione sui risultati del piano di esecuzione effettivo di entrambe le query.

Ecco il DDL per CustPassMaster (quello con l'indice inutilizzato):

CREATE TABLE dbo.CustPassMaster(
    [CustNum] [varchar](8) NOT NULL,
    [Username] [char](15) NOT NULL,
    [Password] [char](15) NOT NULL,
    /* more columns here */
    [VBTerminator] [varchar](1) NOT NULL
) ON [PRIMARY]

CREATE NONCLUSTERED INDEX [IX_dbo_CustPassMaster_CustNum] ON dbo.CustPassMaster
(
    [CustNum] ASC
) WITH (PAD_INDEX = OFF
    , STATISTICS_NORECOMPUTE = OFF
    , SORT_IN_TEMPDB = OFF
    , DROP_EXISTING = OFF
    , ONLINE = OFF
    , ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

E il DDL per CustDataMaster (ho omesso molti campi irrilevanti):

CREATE TABLE dbo.CustDataMaster(
    [CustNum] [varchar](8) NOT NULL,
    /* more columns here */
    [VBTerminator] [varchar](1) NOT NULL
) ON [PRIMARY]

CREATE NONCLUSTERED INDEX [IX_dbo_CustDataMaster_CustNum] ON dbo.CustDataMaster
(
    [CustNum] ASC
)WITH (PAD_INDEX = OFF
    , STATISTICS_NORECOMPUTE = OFF
    , SORT_IN_TEMPDB = OFF
    , DROP_EXISTING = OFF
    , ONLINE = OFF
    , ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

Non ho un indice cluster su nessuna di quelle tabelle, solo un indice non cluster.

Ignora il fatto che i tipi di dati non corrispondono interamente al tipo di dati archiviati. Questi campi sono un backup da un database IBM AS / 400 DB2 e questi sono i tipi di dati compatibili per esso. (Devo essere in grado di interrogare questo database di backup con le stesse identiche query e ottenere il stessi esatti risultati.)

Questi dati vengono utilizzati solo per le SELECTdichiarazioni. Non faccio alcuna dichiarazione INSERT/ UPDATE/ DELETEsu di esso, tranne quando l'applicazione di backup sta copiando i dati dall'AS / 400.


Potrebbe valere la pena leggere questo articolo sul punto di non ritorno da Non cluster a Cluster. sqlskills.com/blogs/kimberly/the-tipping-point-query-answers
Mark Sinkinson,

3
Quindi questa è la differenza. Se la prima query utilizzava il tuo indice, avrebbe dovuto eseguire 65 ricerche. Questo è costoso. La seconda query deve eseguirne solo una.
Aaron Bertrand

Risposte:


18

In genere gli indici verranno utilizzati da SQL Server se lo ritiene più opportuno utilizzare l'indice che utilizzare direttamente la tabella sottostante.

Sembrerebbe probabile che l'ottimizzatore basato sui costi pensi che sarebbe più costoso utilizzare effettivamente l'indice in questione. Potresti vederlo usare l'indice se invece di farlo SELECT *, semplicementeSELECT T1Col1 .

Quando si SELECT *dice a SQL Server di restituire tutte le colonne nella tabella. Per restituire quelle colonne, SQL Server deve leggere le pagine per le righe che corrispondono ai WHEREcriteri dell'istruzione dalla tabella stessa (indice cluster o heap). SQL Server probabilmente pensa che la quantità di letture richieste per ottenere il resto delle colonne dalla tabella significhi che potrebbe anche scansionare direttamente la tabella. Sarebbe utile vedere la query effettiva e il piano di esecuzione effettivo utilizzato dalla query.


3
Quindi una soluzione più ovvia e ottimale sarebbe per me limitare le colonne che seleziono e includerle nella INCLUDEclausola dell'indice?
Der Kommissar,

1
Potrebbe benissimo fare una grande differenza. L'aggiunta di tutte le colonne restituite dalla query alla INCLUDEclausola farà sì che SQL Server utilizzi l'indice. Detto questo, cosa stai cercando di ottimizzare? Mi sembra che la tua tabella abbia una dimensione media delle righe di 100 byte, quindi 5000 righe sono solo circa 500 kb di dati e potrebbe non valere la pena spendere del tempo.
Max Vernon,

1
La dimensione media della riga è 0,30 KB per Table1e 0,53 KB per Table2. Tutti questi dati vengono importati da un AS / 400 (IBM System i) e non ci sono PK su nulla. Oggi ho creato manualmente tutti gli indici dopo che la gente diceva che a volte l'applicazione è piuttosto lenta.
Der Kommissar,

10

Per usare l'indice, perché lo stai facendo select * , SQL Server deve prima leggere ciascuna delle righe dall'indice che corrispondono al valore che hai nella clausola where. Sulla base di questo, otterrà i valori dell'indice cluster per ciascuna riga e quindi dovrà cercare ciascuno di essi separatamente dall'indice cluster (= ricerca chiave). Poiché hai detto che i valori non sono univoci, SQL Server utilizza le statistiche per stimare quante volte deve eseguire questa ricerca chiave.

Molto probabilmente la stima dei costi per la scansione dell'indice non cluster + ricerche chiave supera la stima dei costi per la scansione dell'indice cluster, ed è per questo che l'indice viene ignorato.

È possibile provare a utilizzare set statistics io one quindi utilizzare un suggerimento sull'indice per vedere se il costo I / O è effettivamente inferiore quando si utilizza l'indice o meno. Se la differenza è grande, puoi esaminare le statistiche, se non sono aggiornate.

Inoltre, se il tuo SQL utilizza effettivamente variabili e non i valori esatti, ciò potrebbe essere causato anche dallo sniffing dei parametri (= il valore precedente utilizzato per creare il piano aveva molte righe nella tabella).


1

Questo potrebbe essere il motivo. Gli ottimizzatori sono basati sui costi e decidono quale percorso scegliere in base al "costo" di ciascun percorso di esecuzione. Il costo "maggiore" consiste nel trasferire i dati dal disco alla memoria. Se l'ottimizzatore calcola che ci vuole più tempo per leggere sia l'indice che i dati, potrebbe decidere di saltare l'indice. Più grandi sono le righe, più blocchi di dischi prendono.

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.