Perché la mia clausola WHERE beneficia di una colonna "inclusa"?


12

Secondo questa risposta , a meno che non venga creato un indice sulle colonne utilizzate per limitare, la query non beneficerà di un indice.

Ho questa definizione:

CREATE TABLE [dbo].[JobItems] (
    [ItemId]             UNIQUEIDENTIFIER NOT NULL,
    [ItemState]          INT              NOT NULL,
    [ItemPriority]       INT NOT NULL,
    [CreationTime]       DATETIME         NULL DEFAULT GETUTCDATE(),
    [LastAccessTime]     DATETIME         NULL DEFAULT GETUTCDATE(),
     -- other columns
 );

 CREATE UNIQUE CLUSTERED INDEX [JobItemsIndex]
    ON [dbo].[JobItems]([ItemId] ASC);
 GO

CREATE INDEX [GetItemToProcessIndex]
    ON [dbo].[JobItems]([ItemState], [ItemPriority], [CreationTime])
    INCLUDE (LastAccessTime);
GO

e questa query:

UPDATE TOP (150) JobItems 
SET ItemState = 17 
WHERE 
    ItemState IN (3, 9, 10)
    AND LastAccessTime < DATEADD (day, -2, GETUTCDATE()) 
    AND CreationTime < DATEADD (day, -2, GETUTCDATE());

Ho rivisto il piano reale e c'è solo una ricerca dell'indice con il predicato esattamente come nel WHERE- nessuna "ricerca di segnalibri" aggiuntiva da recuperare LastAccessTimeanche se quest'ultimo è solo "incluso" nell'indice, non parte dell'indice.

Mi sembra che questo comportamento contraddica la regola secondo cui la colonna deve far parte dell'indice e non solo "inclusa".

Il comportamento che osservo è quello giusto? Come posso sapere in anticipo se i miei WHEREvantaggi da una colonna inclusa o se la colonna fa parte dell'indice?


Può ancora cercare in base al ItemStatevalore, tuttavia la ricerca non sarà efficiente come se il tuo indice fosse strutturato come segue(ItemState, CreationTime, LastAccessTime)
Mark Sinkinson,

1
@MarkSinkinson o semplicemente(ItemState, CreationTime) INCLUDE (LastAccessTime)
ypercubeᵀᴹ

@sharptooth la risposta collegata che hai non dice che "" a meno che un indice non sia costruito sulle colonne utilizzate per limitare la query non trarrà vantaggio da un indice "). Dice che un indice attivo (a,b)non è il migliore per una query con SELECT a FROM t WHERE b=5;e che un indice attivo (b) INCLUDE (a)è molto meglio.
ypercubeᵀᴹ

Risposte:


9

Il tuo Predicato è diverso dal tuo Predicato di ricerca.

Un predicato di ricerca viene utilizzato per cercare i dati ordinati nell'indice. In questo caso, eseguirà tre ricerche, una per ogni ItemState a cui sei interessato. Oltre a ciò, i dati sono in ordine ItemPriority, quindi non è possibile eseguire ulteriori operazioni "Seek".

Ma prima che i dati vengano restituiti, controlla ogni riga utilizzando il Predicato, a cui mi riferisco come Predicato residuo. È fatto sui risultati del Predicato di ricerca.

Qualsiasi colonna inclusa non fa parte dei dati ordinati, ma può essere utilizzata per soddisfare il Predicato residuo, senza dover effettuare la Ricerca aggiuntiva.

Puoi vedere il materiale che ho scritto su questo riguardo Sargability. Verificare in particolare una sessione su SQLBits, all'indirizzo http://bit.ly/Sargability

Modifica: per mostrare meglio l'impatto dei Residui, eseguire la query utilizzando il non documentato OPTION (QUERYTRACEON 9130), che separerà il Residuo in un operatore Filtro separato (che in realtà è una versione precedente del piano prima che il residuo venga spostato nell'operatore Cerca). Mostra chiaramente l'impatto di una ricerca inefficace, in base al numero di righe passate a sinistra nel filtro.

Vale anche la pena notare che, a causa della clausola IN su ItemState, i dati che vengono passati a sinistra sono effettivamente nell'ordine ItemState, non nell'ordine ItemPriority. Un indice composito su ItemState seguito da una delle date (ad es. (ItemState, LastAccessTime)) potrebbe essere utilizzato per avere tre Seeks (notare che il Predicate Seek mostra tre ricerche all'interno di un operatore Seek), ciascuna rispetto a due livelli, producendo dati che sono sempre nell'ordine ItemState (ad esempio ItemState = 3 e LastAccessTime meno di qualcosa, quindi ItemState = 9 e LastAccessTime meno di qualcosa, quindi ItemState = 10 e LastAccessTime meno di qualcosa).

Un indice su (ItemState, LastAccesTime, CreationTime) non sarebbe più utile di uno su (ItemState, LastAccessTime) perché il livello CreationTime è utile solo se la tua ricerca è per una particolare combinazione ItemState e LastAccessTime, non un intervallo. Ad esempio come la rubrica non è nell'ordine di FirstName se sei interessato a Cognomi che iniziano con F.

Se si desidera un indice composito ma non si sarà mai in grado di utilizzare le colonne successive in Cerca predicati a causa del modo in cui si usano le colonne precedenti, è possibile averle come colonne incluse, dove occupano meno spazio nella indice (perché sono memorizzati solo al livello foglia dell'indice, non ai livelli più alti) ma possono comunque evitare le ricerche e abituarsi ai predicati residui.

Secondo il termine Predicato residuo - questo è il mio termine per questa proprietà di un Cercatore. Un Merge Join lo definisce esplicitamente un Predicato residuo e Hash Match lo definisce un Probe Residual (che potresti ottenere dalla TSA se combini per l'hash). Ma in una ricerca lo chiamano semplicemente Predicato che lo fa sembrare meno male di quello che è.


3

GetItemToProcessIndex non è completamente ricercabile perché la clausola where è attiva ItemState + LastAccessTime + CreationTime. Le colonne indicizzate e la clausola where sono una corrispondenza non perfetta.

Se si crea un indice di copertura su ItemState + LastAccessTime + CreationTime, per ogni corrispondenza ottenuta da GetItemToProcessIndex, si ottiene anche il valore della chiave primaria (ItemId). Deve solo assicurarsi che la seconda data sia una partita.

Questo è tutto ciò che serve per passare alla posizione della riga sulla sua pagina e aggiornarla.

Con il tuo indice corrente, può aiutare il server a trovare le righe con ItemState che desideri, ma deve comunque leggerle tutte dall'indice per trovare le corrispondenze corrette su LastAccessTime + CreationTime. A seconda dei predicati della data e delle dimensioni dell'insieme corrispondente e di ciò che deve essere escluso, può risultare in un numero molto maggiore di IO rispetto a un indice perfettamente coprente solo sulle 3 colonne che cercherebbe ItemState e la seconda colonna (1a data indicizzata) . Tuttavia, è possibile includere la seconda data nell'indicizzato. Le colonne extra non devono essere indicizzate tra queste 3 anche se potrebbe essere ok come quarta colonna (vedi la risposta di rob sulle colonne extra).

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.