Perché questa query non utilizza il mio indice non cluster e come posso crearla?


12

Come seguito a questa domanda sull'aumento delle prestazioni della query, vorrei sapere se esiste un modo per utilizzare il mio indice per impostazione predefinita.

Questa query viene eseguita in circa 2,5 secondi:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';

Questo ha una durata di circa 33ms:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' 
ORDER BY [DateEntered], [DeviceID];

Esiste un indice cluster sul campo [ID] (pk) e un indice non cluster su [DateEntered], [DeviceID]. La prima query utilizza l'indice cluster, la seconda query utilizza il mio indice non cluster. La mia domanda è divisa in due parti:

  • Perché, poiché entrambe le query hanno una clausola WHERE nel campo [DateEntered], il server utilizza l'indice cluster sul primo, ma non sul secondo?
  • Come posso fare in modo che l'indice non cluster sia usato per impostazione predefinita su questa query anche senza l'ordine? (O perché non dovrei volere quel comportamento?)

DateEntered è un DateTime, in questo caso sto usando la parte della data, ma a volte eseguo una query sia sulla data che sull'ora insieme.
Nate,

Risposte:


9

la prima query esegue una scansione della tabella in base alla soglia precedentemente spiegata in: È possibile aumentare le prestazioni della query su una tabella stretta con milioni di righe?

(molto probabilmente la tua query senza la TOP 1000clausola restituirà più di 46k righe. o alcune dove tra 35k e 46k. (l'area grigia ;-))

la seconda query, deve essere ordinata. Poiché il tuo indice NC viene ordinato nell'ordine desiderato, è più economico per l'ottimizzatore utilizzare quell'indice, quindi per le ricerche dei segnalibri nell'indice cluster per ottenere le colonne mancanti rispetto a fare una scansione dell'indice cluster e quindi è necessario per ordinarlo.

invertire l'ordine delle colonne nella ORDER BYclausola e si torna a una scansione dell'indice cluster poiché l'indice NC è quindi inutile.

modifica ha dimenticato la risposta alla tua seconda domanda, perché NON lo vuoi

L'uso di un indice non di copertura non cluster significa che un rowID viene cercato nell'indice NC e quindi le colonne mancanti devono essere cercate nell'indice cluster (l'indice cluster contiene tutte le colonne di una tabella). Gli IO per cercare le colonne mancanti nell'indice cluster sono IO casuali.

La chiave qui è RANDOM. perché per ogni riga trovata nell'indice NC, i metodi di accesso devono cercare una nuova pagina nell'indice cluster. Questo è casuale e quindi molto costoso.

Ora, d'altra parte, l'ottimizzatore potrebbe anche andare per una scansione dell'indice cluster. Può utilizzare le mappe di allocazione per cercare intervalli di scansione e iniziare a leggere l'indice cluster in blocchi di grandi dimensioni. Questo è sequenziale e molto più economico. (fintanto che la tabella non è frammentata :-)) Il rovescio della medaglia è che è necessario leggere l'intero indice cluster. Questo è dannoso per il buffer e potenzialmente un'enorme quantità di IO. ma ancora, IO sequenziali.

Nel tuo caso, l'ottimizzatore decide da qualche parte tra 35k e 46k righe, è meno costoso per una scansione completa dell'indice cluster. Sì, è sbagliato. E in molti casi con indici ristretti non raggruppati con WHEREclausole non selettive o tabella di grandi dimensioni, ciò va storto. (Il tuo tavolo è peggio, perché è anche un tavolo molto stretto.)

Ora, l'aggiunta di ORDER BYrende più costoso scansionare l'intero indice cluster e quindi ordinare i risultati. Invece, l'ottimizzatore presuppone che sia più economico utilizzare l'indice NC già ordinato e quindi pagare la penalità I / O casuale per le ricerche dei segnalibri.

Quindi il tuo ordine è una soluzione di tipo "suggerimento query" perfetto. MA, ad un certo punto, una volta che i risultati della tua query sono così grandi, la penalità per gli I / O casuali della ricerca dei segnalibri sarà così grande da rallentare. Presumo che l'ottimizzatore cambierà i piani per la scansione dell'indice cluster prima di quel punto, ma non si sa mai con certezza.

Nel tuo caso, fintanto che i tuoi inserti sono ordinati per entereddate, come discusso nella chat e nella domanda precedente (vedi link), è meglio creare l'indice cluster nella colonna della data immessa.


20

L'espressione della query mediante una sintassi diversa può talvolta aiutare a comunicare all'ottimizzatore il desiderio di utilizzare un indice non cluster. Dovresti trovare il modulo sottostante che ti dà il piano che desideri:

SELECT
    [ID],
    [DeviceID],
    [IsPUp],
    [IsWebUp],
    [IsPingUp],
    [DateEntered]
FROM [dbo].[Heartbeats]
WHERE
    [ID] IN
(
    -- Keys
    SELECT TOP (1000)
        [ID]
    FROM [dbo].[Heartbeats]
    WHERE 
        [DateEntered] >= CONVERT(datetime, '2011-08-30', 121)
        AND [DateEntered]  < CONVERT(datetime, '2011-08-31', 121)
);

Piano di query

Confronta quel piano con quello prodotto quando l'indice non cluster viene forzato con un suggerimento:

SELECT TOP (1000) 
    * 
FROM [dbo].[Heartbeats] WITH (INDEX(CommonQueryIndex))
WHERE 
    [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';

Piano di suggerimenti sull'indice forzato

I piani sono essenzialmente gli stessi (una ricerca chiave non è altro che una ricerca sull'indice cluster). Entrambi i moduli del piano eseguiranno solo una ricerca sull'indice non cluster e un massimo di 1000 ricerche nell'indice cluster.

La differenza importante sta nella posizione dell'operatore Top. Posizionato tra le due ricerche, la parte superiore impedisce all'ottimizzatore di sostituire le due operazioni di ricerca con una scansione logicamente equivalente dell'indice cluster. L'ottimizzatore funziona sostituendo parti di un piano logico con operazioni relazionali equivalenti. Top non è un operatore relazionale, quindi la riscrittura impedisce la trasformazione in una scansione di indice cluster. Se l'ottimizzatore fosse in grado di riposizionare l'operatore Top, preferirebbe comunque la scansione rispetto alla ricerca + ricerca a causa del modo in cui funziona la stima dei costi.

Costi di scansioni e ricerche

A un livello molto alto, il modello di costo dell'ottimizzatore per le scansioni e le ricerche è abbastanza semplice: si stima che 320 ricerche casuali costino come leggere 1350 pagine in una scansione. Questo probabilmente ha poca somiglianza con le capacità hardware di qualsiasi particolare moderno sistema I / O, ma funziona abbastanza bene come modello pratico.

Il modello fa anche una serie di ipotesi di semplificazione, una delle principali è che si presume che ogni query inizi senza dati o pagine di indice già nella cache. L'implicazione è che ogni I / O si tradurrà in un I / O fisico, anche se questo raramente sarà il caso nella pratica. Anche con una cache fredda, il prelavaggio e il read-ahead significano che le pagine necessarie sono effettivamente molto probabilmente in memoria quando il processore di query ne ha bisogno.

Un'altra considerazione è che la prima richiesta per una riga che non è in memoria provocherà il recupero dell'intera pagina dal disco. Le successive richieste di righe sulla stessa pagina molto probabilmente non subiranno un I / O fisico. Il modello di costing contiene una logica per tenere conto di effetti come questo, ma non è perfetto.

Tutto ciò (e altro) significa che l'ottimizzatore tende a passare a una scansione prima di quanto probabilmente dovrebbe. L'I / O casuale è solo "molto più costoso" degli I / O "sequenziali" se si verifica un'operazione fisica: l'accesso alle pagine in memoria è davvero molto veloce. Anche dove è richiesta una lettura fisica, una scansione potrebbe non provocare affatto letture sequenziali a causa della frammentazione e le ricerche possono essere collocate in modo tale che il modello sia essenzialmente sequenziale. Aggiungete a ciò le mutevoli caratteristiche prestazionali dei moderni sistemi I / O (specialmente allo stato solido) e tutto inizia a sembrare molto traballante.

Gol

La presenza di un operatore Top in un piano modifica l'approccio dei costi. L'ottimizzatore è abbastanza intelligente da sapere che la ricerca di 1000 righe usando una scansione probabilmente non richiederà la scansione dell'intero indice cluster - può fermarsi non appena sono state trovate 1000 righe. Imposta un "obiettivo di riga" di 1000 righe nell'operatore principale e utilizza le informazioni statistiche per tornare da lì per stimare quante righe si aspetta dall'origine della riga (una scansione in questo caso). Ho scritto sui dettagli di questo calcolo qui .

Le immagini in questa risposta sono state create utilizzando SQL Sentry Plan Explorer .

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.