Il piano di esecuzione mostra un'operazione CONVERT_IMPLICIT costosa. Posso risolvere questo problema con l'indicizzazione o devo cambiare la tabella?


24

Ho una visione molto importante, molto lenta, che include alcune condizioni davvero brutte come questa nella sua clausola where. Sono anche consapevole del fatto che i join sono lordi e lenti su varchar(13)campi invece di interi numeri interi, ma vorrei migliorare la semplice query di seguito che utilizza questa vista:

CREATE VIEW [dbo].[vwReallySlowView]  AS  
AS  
SELECT     
  I.booking_no_v32 AS bkno, 
  I.trans_type_v41 AS trantype, 
  B.Assigned_to_v61 AS Assignbk, 
  B.order_date AS dateo, B.HourBooked AS HBooked,   
  B.MinBooked AS MBooked, B.SecBooked AS SBooked, 
  I.prep_on AS Pon, I.From_locn AS Flocn, 
  I.Trans_to_locn AS TTlocn,   
                      (CASE I.prep_on WHEN 'Y' THEN I.PDate ELSE I.FirstDate END) AS PrDate, I.PTimeH AS PrTimeH, I.PTimeM AS PrTimeM,   
                      (CASE WHEN I.RetnDate < I.FirstDate THEN I.FirstDate ELSE I.RetnDate END) AS RDatev, I.bit_field_v41 AS bitField, I.FirstDate AS FDatev, I.BookDate AS DBooked,   
                      I.TimeBookedH AS TBookH, I.TimeBookedM AS TBookM, I.TimeBookedS AS TBookS, I.del_time_hour AS dth, I.del_time_min AS dtm, I.return_to_locn AS rtlocn,   
                      I.return_time_hour AS rth, I.return_time_min AS rtm, (CASE WHEN I.Trans_type_v41 IN (6, 7) AND (I.Trans_qty < I.QtyCheckedOut)   
                      THEN 0 WHEN I.Trans_type_v41 IN (6, 7) AND (I.Trans_qty >= I.QtyCheckedOut) THEN I.Trans_Qty - I.QtyCheckedOut ELSE I.trans_qty END) AS trqty,   
                      (CASE WHEN I.Trans_type_v41 IN (6, 7) THEN 0 ELSE I.QtyCheckedOut END) AS MyQtycheckedout, (CASE WHEN I.Trans_type_v41 IN (6, 7)   
                      THEN 0 ELSE I.QtyReturned END) AS retqty, I.ID, B.BookingProgressStatus AS bkProg, I.product_code_v42, I.return_to_locn, I.AssignTo, I.AssignType,   
                      I.QtyReserved, B.DeprepOn,  
        (CASE  B.DeprepOn       
        WHEN 1 THEN  B.DeprepDateTime     
        ELSE   I.RetnDate  
           END)  AS DeprepDateTime, I.InRack 
FROM         dbo.tblItemtran AS I 

INNER JOIN  -- booking_no = varchar(13)
         dbo.tblbookings AS B ON B.booking_no = I.booking_no_v32  --  string inner-join

INNER JOIN  -- product_code = varchar(13) 
        dbo.tblInvmas AS M ON I.product_code_v42 = M.product_code  --  string inner-join

WHERE     (I.trans_type_v41 NOT IN (2, 3, 7, 18, 19, 20, 21, 12, 13, 22)) AND (I.trans_type_v41 NOT IN (6, 7)) AND (I.bit_field_v41 & 4 = 0) OR  
                      (I.trans_type_v41 NOT IN (6, 7)) AND (I.bit_field_v41 & 4 = 0) AND (B.BookingProgressStatus = 1) OR  
                      (I.trans_type_v41 IN (6, 7)) AND (I.bit_field_v41 & 4 = 0) AND (I.QtyCheckedOut = 0) OR  
                      (I.trans_type_v41 IN (6, 7)) AND (I.bit_field_v41 & 4 = 0) AND (I.QtyCheckedOut > 0) AND (I.trans_qty - (I.QtyCheckedOut - I.QtyReturned) > 0)  

Questa vista viene generalmente utilizzata in questo modo:

select * from vwReallySlowView
where product_code_v42  = 'LIGHTBULB100W'  -- find "100 watt lightbulb" rows

Quando lo eseguo ottengo questo articolo del piano di esecuzione che costa dal 20 all'80% del costo totale del batch, con predicato CONVERT_IMPLICIT( .... &(4))che mostra che sembra essere molto lento nel fare questi bitwise boolean testssimili (I.ibitfield & 4 = 0).

Non sono un esperto di MS SQL o di tipo DBA in generale, poiché sono uno sviluppatore di software non SQL per la maggior parte del tempo. Ma ho il sospetto che tali combinazioni bit per bit siano una cattiva idea e che sarebbe stato meglio avere campi booleani discreti.

Potrei in qualche modo migliorare questo indice che ho, per gestire meglio questa vista senza cambiare lo schema (che è già in produzione in migliaia di posizioni) o devo cambiare la tabella sottostante che ha diversi valori booleani impacchettati in un numero intero bit_field_v41, per risolvere questo problema ?

Ecco il mio indice cluster su tblItemtrancui viene scansionato in questo piano di esecuzione:

-- goal:  speed up  select * from vwReallySlowView where productcode  = 'X'
CREATE CLUSTERED INDEX [idxtblItemTranProductCodeAndTransType] ON [dbo].[tblItemtran] 
(
    [product_code_v42] ASC,  -- varchar(13)
    [trans_type_v41] ASC     -- int
)WITH ( PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, 
        IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, 
        ALLOW_PAGE_LOCKS  = ON) 
ON [PRIMARY]

Ecco il piano di esecuzione, per uno degli altri prodotti che comporta un costo del 27% su questo CONVERT_IMPLICITpredicato. aggiornare Nota che in questo caso il mio nodo peggiore ora è "hash match" su un inner join, che costa il 34%. Credo che questo sia un costo che non posso evitare a meno che non possa evitare di fare join su stringhe che al momento non posso liberarsi di. Entrambe le INNER JOINoperazioni nella vista sopra sono sui varchar(13)campi.

Ingrandito nell'angolo in basso a destra:

inserisci qui la descrizione dell'immagine

L'intero piano di esecuzione come .sqlplan è disponibile su skydrive. Questa immagine è solo una panoramica visiva. Clicca qui per vedere l'immagine da sola.

inserisci qui la descrizione dell'immagine

Aggiornamento pubblicato intero piano di esecuzione. Non riesco a trovare quale product_codevalore sia patologicamente negativo, ma un modo per farlo è quello select count(*) from viewdi fare un singolo prodotto. Ma i prodotti che vengono utilizzati solo nel 5% dei record nella tabella sottostante o meno sembrano mostrare costi molto più bassi CONVERT_IMPLICIT nell'operazione. Se avessi risolto l'SQL qui, penso che prenderei la WHEREclausola grossolana nella vista e calcolerei e memorizzerei il risultato di quella gigantesca condizione della clausola where come campo bit "IncludeMeInTheView", nella tabella sottostante . Presto, problema risolto, vero?



Il .SQLPLAN che ho pubblicato è molto meno patologico del caso 432k / 17k nell'immagine originale. Mi scuso non riesco più a trovare il product_codevalore che ho usato per generare quel 87%caso patologico con i miei dati. Le immagini ora mostrano 27%. Ancora una volta, mi scuso per la confusione dovuta alle mie modifiche.
Warren P

Risposte:


49

Non si dovrebbe fare troppo affidamento sulle percentuali di costo nei piani di esecuzione. Questi sono sempre costi stimati , anche nei piani di post-esecuzione con numeri "reali" per cose come il conteggio delle righe. I costi stimati si basano su un modello che sembra funzionare abbastanza bene per lo scopo a cui è destinato: consentire all'ottimizzatore di scegliere tra diversi piani di esecuzione dei candidati per la stessa query. Le informazioni sui costi sono interessanti e un fattore da considerare, ma raramente dovrebbero essere una metrica primaria per l'ottimizzazione delle query. L'interpretazione delle informazioni sul piano di esecuzione richiede una visione più ampia dei dati presentati.

ItemTran Clustered Index Seek Operator

Ricerca nell'indice cluster di ItemTran

Questo operatore ha davvero due operazioni in una. Prima un'operazione di ricerca indice trova tutte le righe che corrispondono al predicato product_code_v42 = 'M10BOLT', quindi a ogni riga viene bit_field_v41 & 4 = 0applicato il predicato residuo . Esiste una conversione implicita del bit_field_v41tipo di base ( tinyinto smallint) ininteger .

La conversione si verifica perché l' operatore AND bit per bit (&) richiede che entrambi gli operandi siano dello stesso tipo. Il tipo implicito del valore costante '4' è intero e le regole di precedenza del tipo di dati indicano che il bit_field_v41valore del campo con priorità inferiore viene convertito.

Il problema (così com'è) viene facilmente corretto scrivendo il predicato come bit_field_v41 & CONVERT(tinyint, 4) = 0- il che significa che il valore costante ha la priorità più bassa e viene convertito (durante la piegatura costante) anziché il valore della colonna. Se l' bit_field_v41è tinyintsenza conversioni si verificano a tutti. Allo stesso modo, CONVERT(smallint, 4)potrebbe essere usato se lo bit_field_v41è smallint. Detto questo, la conversione non è un problema di prestazioni in questo caso, ma è comunque buona pratica abbinare i tipi ed evitare conversioni implicite ove possibile.

La maggior parte del costo stimato di questa ricerca dipende dalle dimensioni della tabella di base. Mentre la chiave di indice cluster è di per sé ragionevolmente stretta, la dimensione di ogni riga è grande. Non viene fornita una definizione per la tabella, ma solo le colonne utilizzate nella vista si sommano a una larghezza di riga significativa. Poiché l'indice cluster include tutte le colonne, la distanza tra le chiavi dell'indice cluster è la larghezza della riga , non la larghezza delle chiavi dell'indice . L'uso dei suffissi di versione su alcune colonne suggerisce che la tabella reale ha ancora più colonne per le versioni precedenti.

Osservando la ricerca, il predicato residuo e le colonne di output, le prestazioni di questo operatore potrebbero essere verificate separatamente creando la query equivalente ( 1 <> 2è un trucco per impedire l'auto-parametrizzazione, la contraddizione viene rimossa dall'ottimizzatore e non appare nel piano di query):

SELECT
    it.booking_no_v32,
    it.QtyCheckedOut,
    it.QtyReturned,
    it.Trans_qty,
    it.trans_type_v41
FROM dbo.tblItemTran AS it
WHERE
    1 <> 2
    AND it.product_code_v42 = 'M10BOLT'
    AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0;

Le prestazioni di questa query con una cache di dati fredda sono interessanti, poiché la lettura anticipata sarebbe influenzata dalla frammentazione della tabella (indice cluster). La chiave di clustering per questa tabella invita la frammentazione, quindi potrebbe essere importante mantenere (riorganizzare o ricostruire) questo indice regolarmente e utilizzare uno FILLFACTORspazio appropriato per consentire lo spazio per le nuove righe tra le finestre di manutenzione dell'indice.

Ho eseguito un test dell'effetto della frammentazione sul read-ahead utilizzando i dati di esempio generati utilizzando SQL Data Generator . Utilizzando gli stessi conteggi delle righe della tabella mostrati nel piano di query della domanda, un indice cluster altamente frammentato ha richiesto SELECT * FROM view15 secondi dopo DBCC DROPCLEANBUFFERS. Lo stesso test nelle stesse condizioni con un indice cluster appena ricostruito sulla tabella ItemTrans è stato completato in 3 secondi.

Se i dati della tabella sono in genere interamente nella cache, il problema della frammentazione è molto meno importante. Ma, anche con una bassa frammentazione, le ampie righe della tabella potrebbero significare che il numero di letture logiche e fisiche è molto più alto di quanto ci si potrebbe aspettare. Potresti anche provare ad aggiungere e rimuovere l'esplicito CONVERTper convalidare la mia aspettativa che il problema di conversione implicita non è importante qui, tranne come violazione delle migliori pratiche.

Più precisamente è il numero stimato di righe che escono dall'operatore di ricerca. La stima del tempo di ottimizzazione è di 165 righe, ma 4.226 sono stati prodotti al momento dell'esecuzione. Ritornerò su questo punto in seguito, ma il motivo principale della discrepanza è che la selettività del predicato residuo (che coinvolge il bit-AND) è molto difficile da prevedere per l'ottimizzatore - in effetti si ricorre all'ipotesi.

Operatore filtro

Operatore filtro

Sto mostrando qui il predicato del filtro principalmente per illustrare come le due NOT INliste vengono combinate, semplificate e quindi espanse, e anche per fornire un riferimento per la seguente discussione sulla corrispondenza dell'hash. La query di prova della ricerca può essere espansa per incorporare i suoi effetti e determinare l'effetto dell'operatore Filter sulle prestazioni:

SELECT
    it.booking_no_v32,
    it.trans_type_v41,
    it.Trans_qty,
    it.QtyReturned,
    it.QtyCheckedOut
FROM dbo.tblItemTran AS it
WHERE
    it.product_code_v42 = 'M10BOLT'
    AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
    AND
    (
        (
            it.trans_type_v41 NOT IN (2, 3, 6, 7, 18, 19, 20, 21, 12, 13, 22)
            AND it.trans_type_v41 NOT IN (6, 7)
        )
        OR
        (
            it.trans_type_v41 NOT IN (6, 7)
        )
        OR 
        (
            it.trans_type_v41 IN (6, 7)
            AND it.QtyCheckedOut = 0
        )
        OR 
        (
            it.trans_type_v41 IN (6, 7)
            AND it.QtyCheckedOut > 0
            AND it.trans_qty - (it.QtyCheckedOut - it.QtyReturned) > 0
        )
    );

L'operatore Calcola scalare nel piano definisce la seguente espressione (il calcolo stesso viene rinviato fino a quando il risultato non viene richiesto da un operatore successivo):

[Expr1016] = (trans_qty - (QtyCheckedOut - QtyReturned))

L'operatore Hash Match

L'esecuzione di un join sui tipi di dati carattere non è la ragione dell'elevato costo stimato di questo operatore. La descrizione comandi SSMS mostra solo una voce della sonda per chiavi hash, ma i dettagli importanti si trovano nella finestra Proprietà SSMS.

L'operatore Hash Match crea una tabella hash utilizzando i valori della booking_no_v32colonna (Hash Keys Build) dalla tabella ItemTran e quindi i probe per le corrispondenze utilizzando la booking_nocolonna (Hash Keys Probe) dalla tabella Bookings. Anche la descrizione comandi SSMS mostrerebbe normalmente un residuo sonda, ma il testo è troppo lungo per una descrizione comandi e viene semplicemente omesso.

Un residuo di sonda è simile al residuo visto dopo che l'indice ha cercato prima; il predicato residuo viene valutato su tutte le righe che corrispondono all'hash per determinare se la riga deve essere passata all'operatore principale. Trovare corrispondenze hash in una tabella hash ben bilanciata è estremamente veloce, ma l'applicazione di un predicato residuo complesso ad ogni riga che corrisponde è piuttosto lenta al confronto. La descrizione del comando Hash Match in Plan Explorer mostra i dettagli, inclusa l'espressione Residue della sonda:

Operatore Hash Match

Il predicato residuo è complesso e include il controllo dello stato di avanzamento della prenotazione ora che la colonna è disponibile nella tabella delle prenotazioni. La descrizione comandi mostra anche la stessa discrepanza tra il numero di righe stimato e quello effettivo visto in precedenza nella ricerca dell'indice. Può sembrare strano che gran parte del filtraggio venga eseguito due volte, ma questo è solo l'ottimizzatore che è ottimista. Non si aspetta che le parti del filtro che possono essere spinte giù dal piano dal residuo della sonda eliminino eventuali righe (le stime del conteggio delle righe sono le stesse prima e dopo il filtro), ma l'ottimizzatore sa che potrebbe esserci un errore. La possibilità di filtrare le righe in anticipo (riducendo il costo dell'hash join) vale il piccolo costo del filtro aggiuntivo. L'intero filtro non può essere spinto verso il basso perché include un test su una colonna dalla tabella delle prenotazioni, ma la maggior parte può esserlo.

Il sottostima del conteggio delle righe è un problema per l'operatore Hash Match poiché la quantità di memoria riservata per la tabella hash si basa sul numero stimato di righe. Laddove la memoria è troppo piccola per la dimensione della tabella hash richiesta in fase di esecuzione (a causa del maggior numero di righe), la tabella hash si riversa ricorsivamente nella memoria fisica tempdb , spesso con prestazioni molto scarse. Nel peggiore dei casi, il motore di esecuzione si arresta in modo ricorsivo rovesciando secchi di hash e ricorre a un rallentamentoalgoritmo di salvataggio. Lo spargimento di hash (ricorsivo o di salvataggio) è la causa più probabile dei problemi di prestazioni indicati nella domanda (non colonne di join di tipo carattere o conversioni implicite). La causa principale sarebbe che il server sta riservando memoria insufficiente per la query in base a una stima errata del conteggio delle righe (cardinalità).

Purtroppo, prima di SQL Server 2012, nel piano di esecuzione non vi è alcuna indicazione che un'operazione di hashing abbia superato la sua allocazione di memoria (che non può crescere dinamicamente dopo essere stata prenotata prima dell'avvio dell'esecuzione, anche se il server ha masse di memoria libera) e ha dovuto versare tempdb. È possibile monitorare la classe di eventi di avviso hash utilizzando Profiler, ma può essere difficile correlare gli avvisi con una query specifica.

Correggere i problemi

Le tre problematiche sono la frammentazione, la complessa sonda residua nell'operatore di corrispondenza hash e la stima errata della cardinalità derivante dall'indovinare la ricerca dell'indice.

Soluzione consigliata

Controllare la frammentazione e correggerla se necessario, programmando la manutenzione per garantire che l'indice rimanga organizzato in modo accettabile. Il modo normale per correggere la stima della cardinalità è fornire statistiche. In questo caso, l'ottimizzatore necessita di statistiche per la combinazione ( product_code_v42, bitfield_v41 & 4 = 0). Non possiamo creare direttamente statistiche su un'espressione, quindi dobbiamo prima creare una colonna calcolata per l'espressione del campo bit, quindi creare le statistiche manuali multi-colonna:

ALTER TABLE dbo.tblItemTran
ADD Bit3 AS bit_field_v41 & CONVERT(tinyint, 4);

CREATE STATISTICS [stats dbo.ItemTran (product_code_v42, Bit3)]
ON dbo.tblItemTran (product_code_v42, Bit3);

La definizione del testo della colonna calcolata deve corrispondere esattamente al testo nella definizione della vista per poter utilizzare le statistiche, pertanto la correzione della vista per eliminare la conversione implicita deve essere eseguita contemporaneamente e fare attenzione a garantire una corrispondenza testuale.

Le statistiche multi-colonna dovrebbero comportare stime molto migliori, riducendo notevolmente la possibilità che l'operatore di hash match utilizzi lo spill ricorsivo o l'algoritmo di salvataggio. Aggiungendo la colonna calcolata (che è un'operazione di soli metadati e non occupa spazio nella tabella poiché non è contrassegnata PERSISTED) e le statistiche multi-colonna è la mia ipotesi migliore per una prima soluzione.

Quando si risolvono problemi relativi alle prestazioni delle query, è importante misurare elementi quali tempo trascorso, utilizzo della CPU, letture logiche, letture fisiche, tipi di attesa e durate ... e così via. Può anche essere utile eseguire parti della query separatamente per convalidare le cause sospette, come mostrato sopra.

In alcuni ambienti, dove una vista aggiornata dei dati non è importante, può essere utile eseguire un processo in background che materializzi l'intera vista in una tabella di istantanee ogni tanto. Questa tabella è solo una normale tabella di base e può essere indicizzata per le query di lettura senza preoccuparsi di influire sulle prestazioni degli aggiornamenti.

Visualizza indicizzazione

Non essere tentato di indicizzare direttamente la vista originale. Le prestazioni di lettura saranno incredibilmente veloci (una singola ricerca su un indice della vista) ma (in questo caso) tutti i problemi di prestazioni nei piani di query esistenti verranno trasferiti alle query che modificano una delle colonne della tabella a cui fa riferimento la vista. Le query che cambiano le righe della tabella di base avranno un impatto molto grave.

Soluzione avanzata con una vista indicizzata parziale

Esiste una soluzione parziale di vista indicizzata per questa particolare query che corregge le stime di cardinalità e rimuove i residui di filtro e sonda, ma si basa su alcuni presupposti sui dati (principalmente la mia ipotesi sullo schema) e richiede l'implementazione di esperti, in particolare per quanto riguarda indici per supportare i piani di manutenzione della vista indicizzata. Condivido il codice seguente per interesse, non ti propongo di implementarlo senza analisi e test molto accurati .

-- Indexed view to optimize the main view
CREATE VIEW dbo.V1
WITH SCHEMABINDING
AS
SELECT
    it.ID,
    it.product_code_v42,
    it.trans_type_v41,
    it.booking_no_v32,
    it.Trans_qty,
    it.QtyReturned,
    it.QtyCheckedOut,
    it.QtyReserved,
    it.bit_field_v41,
    it.prep_on,
    it.From_locn,
    it.Trans_to_locn,
    it.PDate,
    it.FirstDate,
    it.PTimeH,
    it.PTimeM,
    it.RetnDate,
    it.BookDate,
    it.TimeBookedH,
    it.TimeBookedM,
    it.TimeBookedS,
    it.del_time_hour,
    it.del_time_min,
    it.return_to_locn,
    it.return_time_hour,
    it.return_time_min,
    it.AssignTo,
    it.AssignType,
    it.InRack
FROM dbo.tblItemTran AS it
JOIN dbo.tblBookings AS tb ON
    tb.booking_no = it.booking_no_v32
WHERE
    (
        it.trans_type_v41 NOT IN (2, 3, 7, 18, 19, 20, 21, 12, 13, 22)
        AND it.trans_type_v41 NOT IN (6, 7)
        AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
    )
    OR
    (
        it.trans_type_v41 NOT IN (6, 7)
        AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
        AND tb.BookingProgressStatus = 1
    )
    OR 
    (
        it.trans_type_v41 IN (6, 7)
        AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
        AND it.QtyCheckedOut = 0
    )
    OR 
    (
        it.trans_type_v41 IN (6, 7)
        AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
        AND it.QtyCheckedOut > 0
        AND it.trans_qty - (it.QtyCheckedOut - it.QtyReturned) > 0
    );
GO
CREATE UNIQUE CLUSTERED INDEX cuq ON dbo.V1 (product_code_v42, ID);
GO

La vista esistente è stata ottimizzata per utilizzare la vista indicizzata sopra:

CREATE VIEW [dbo].[vwReallySlowView2]
AS
SELECT
    I.booking_no_v32 AS bkno,
    I.trans_type_v41 AS trantype,
    B.Assigned_to_v61 AS Assignbk,
    B.order_date AS dateo,
    B.HourBooked AS HBooked,
    B.MinBooked AS MBooked,
    B.SecBooked AS SBooked,
    I.prep_on AS Pon,
    I.From_locn AS Flocn,
    I.Trans_to_locn AS TTlocn,
    CASE I.prep_on 
        WHEN 'Y' THEN I.PDate
        ELSE I.FirstDate
    END AS PrDate,
    I.PTimeH AS PrTimeH,
    I.PTimeM AS PrTimeM,
    CASE
        WHEN I.RetnDate < I.FirstDate 
        THEN I.FirstDate 
        ELSE I.RetnDate
    END AS RDatev,
    I.bit_field_v41 AS bitField,
    I.FirstDate AS FDatev,
    I.BookDate AS DBooked,
    I.TimeBookedH AS TBookH,
    I.TimeBookedM AS TBookM,
    I.TimeBookedS AS TBookS,
    I.del_time_hour AS dth,
    I.del_time_min AS dtm,
    I.return_to_locn AS rtlocn,
    I.return_time_hour AS rth,
    I.return_time_min AS rtm,
    CASE
        WHEN
            I.Trans_type_v41 IN (6, 7) 
            AND I.Trans_qty < I.QtyCheckedOut
            THEN 0 
        WHEN
            I.Trans_type_v41 IN (6, 7)
            AND I.Trans_qty >= I.QtyCheckedOut
            THEN I.Trans_Qty - I.QtyCheckedOut
        ELSE
            I.trans_qty
    END AS trqty,
    CASE
        WHEN I.Trans_type_v41 IN (6, 7)
        THEN 0
        ELSE I.QtyCheckedOut
    END AS MyQtycheckedout,
    CASE
        WHEN I.Trans_type_v41 IN (6, 7)
        THEN 0
        ELSE I.QtyReturned
    END AS retqty,
    I.ID,
    B.BookingProgressStatus AS bkProg,
    I.product_code_v42,
    I.return_to_locn,
    I.AssignTo,
    I.AssignType,
    I.QtyReserved,
    B.DeprepOn,
    CASE B.DeprepOn
        WHEN 1 THEN B.DeprepDateTime
        ELSE I.RetnDate
    END AS DeprepDateTime,
    I.InRack
FROM dbo.V1 AS I WITH (NOEXPAND)
JOIN dbo.tblbookings AS B ON
    B.booking_no = I.booking_no_v32
JOIN dbo.tblInvmas AS M ON
    I.product_code_v42 = M.product_code;

Esempio di query e piano di esecuzione:

SELECT
    vrsv.*
FROM dbo.vwReallySlowView2 AS vrsv
WHERE vrsv.product_code_v42 = 'M10BOLT';

Nuovo piano di esecuzione

Nel nuovo piano, la corrispondenza hash non ha predicato residuo , non esiste un filtro complesso , nessun predicato residuo nella ricerca della vista indicizzata e le stime della cardinalità sono esattamente corrette.

Come esempio di come sarebbero interessati i piani di inserimento / aggiornamento / eliminazione, questo è il piano per un inserimento nella tabella ItemTrans:

Inserisci piano

La sezione evidenziata è nuova e necessaria per la manutenzione della vista indicizzata. Lo spool della tabella riproduce le righe della tabella di base inserite per la manutenzione della vista indicizzata. Ogni riga viene unita alla tabella delle prenotazioni utilizzando una ricerca di indice cluster, quindi un filtro applica i WHEREpredicati della clausola complessa per vedere se la riga deve essere aggiunta alla vista. In tal caso, viene eseguito un inserimento nell'indice cluster della vista.

Lo stesso SELECT * FROM viewtest eseguito in precedenza è stato completato in 150 ms con la vista indicizzata in atto.

Ultima cosa: noto che il tuo server 2008 R2 è ancora su RTM. Non risolverà i tuoi problemi di prestazioni, ma Service Pack 2 per 2008 R2 è disponibile da luglio 2012 e ci sono molti buoni motivi per rimanere il più aggiornati possibile con i service pack.

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.