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)
);
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';
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 .