C'è qualche plausibile vantaggio per l'iteratore di spool nel primo piano?
Questo dipende da ciò che consideri "plausibile", ma la risposta in base al modello di costo è sì. Naturalmente questo è vero, perché l'ottimizzatore sceglie sempre il piano più economico che trova.
La vera domanda è perché il modello di costo considera il piano con la bobina molto più economico del piano senza. Prendi in considerazione i piani stimati creati per una nuova tabella (dallo script) prima di aggiungere eventuali righe all'archivio delta:
DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE);
Il costo stimato per questo piano è di 771.734 unità :
Il costo è quasi interamente associato all'eliminazione dell'indice cluster, poiché le eliminazioni dovrebbero comportare una grande quantità di I / O casuali. Questa è solo la logica generica che si applica a tutte le modifiche ai dati. Ad esempio, si presume che una serie non ordinata di modifiche a un indice b-tree comporti un I / O in gran parte casuale, con un costo I / O elevato associato.
I piani di modifica dei dati possono includere un ordinamento per presentare le righe in un ordine che promuoverà l'accesso sequenziale, proprio per questi motivi di costo. L'impatto è esacerbato in questo caso perché la tabella è partizionata. Molto partizionato, in effetti; la tua sceneggiatura ne crea 15.000. Gli aggiornamenti casuali a una tabella molto partizionata hanno un costo particolarmente elevato poiché anche il prezzo per cambiare partizioni (set di righe) a flusso intermedio ha un costo elevato.
L'ultimo fattore importante da considerare è che la semplice query di aggiornamento sopra (dove "aggiornamento" indica qualsiasi operazione di modifica dei dati, inclusa un'eliminazione) si qualifica per un'ottimizzazione denominata "condivisione del set di righe", in cui lo stesso set di righe interno viene utilizzato sia per la scansione che per aggiornare la tabella. Il piano di esecuzione mostra ancora due operatori separati, ma tuttavia viene utilizzato solo un set di righe.
Ne parlo perché essere in grado di applicare questa ottimizzazione significa che l'ottimizzatore prende un percorso di codice che semplicemente non considera i potenziali benefici dell'ordinamento esplicito per ridurre il costo dell'I / O casuale. Dove la tabella è un b-tree, questo ha senso, perché la struttura è intrinsecamente ordinata, quindi la condivisione del set di righe offre automaticamente tutti i potenziali vantaggi.
La conseguenza importante è che la logica dei costi per l'operatore di aggiornamento non considera questo vantaggio di ordinazione (promuovendo l'I / O sequenziale o altre ottimizzazioni) in cui l'oggetto sottostante è l'archivio di colonne. Questo perché le modifiche all'archivio delle colonne non vengono eseguite sul posto; usano un negozio delta. Il modello di costo riflette quindi una differenza tra gli aggiornamenti del set di righe condivisi su b-tree rispetto a quelli delle colonne.
Tuttavia, nel caso particolare di un columnstore partizionato (molto!), Potrebbe esserci comunque un vantaggio nell'ordinamento conservato, in quanto l'esecuzione di tutti gli aggiornamenti su una partizione prima di passare alla successiva potrebbe essere ancora vantaggiosa dal punto di vista I / O .
La logica dei costi standard viene riutilizzata per gli archivi di colonne qui, quindi un piano che preserva l'ordinamento delle partizioni (anche se non l'ordine all'interno di ciascuna partizione) è ridotto. Possiamo vederlo sulla query di test usando il flag di traccia 2332 non documentato per richiedere un input ordinato per l'operatore di aggiornamento. Ciò imposta la DMLRequestSort
proprietà su true durante l'aggiornamento e impone all'ottimizzatore di produrre un piano che fornisce tutte le righe per una partizione prima di passare alla successiva:
DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE, QUERYTRACEON 2332);
Il costo stimato per questo piano è molto più basso, a 52.5174 unità:
Questa riduzione dei costi è dovuta al minor costo I / O stimato all'aggiornamento. Lo spool introdotto non svolge alcuna funzione utile, tranne per il fatto che può garantire l'output in ordine di partizione, come richiesto dall'aggiornamento con DMLRequestSort = true
(la scansione seriale di un indice di archivio di colonne non può fornire questa garanzia). Il costo della bobina stessa è considerato relativamente basso, soprattutto se confrontato con la riduzione (probabilmente non realistica) dei costi all'aggiornamento.
La decisione se richiedere l'input ordinato per l'operatore di aggiornamento viene presa molto presto nell'ottimizzazione delle query. Le euristiche utilizzate in questa decisione non sono mai state documentate, ma possono essere determinate attraverso prove ed errori. Sembra che la dimensione di qualsiasi negozio delta sia un input per questa decisione. Una volta effettuata, la scelta è permanente per la compilazione della query. Nessun USE PLAN
suggerimento avrà successo: il target del piano o ha ordinato input per l'aggiornamento, oppure no.
Esiste un altro modo per ottenere un piano a basso costo per questa query senza limitare artificialmente la stima della cardinalità. Una stima sufficientemente bassa per evitare che lo spool causi probabilmente un DMLRequestSort falso, con un costo del piano stimato molto elevato a causa dell'I / O casuale previsto. Un'alternativa è utilizzare il flag di traccia 8649 (piano parallelo) in combinazione con 2332 (DMLRequestSort = true):
DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE, QUERYTRACEON 2332, QUERYTRACEON 8649);
Ciò si traduce in un piano che utilizza la scansione parallela in modalità batch per partizione e uno scambio Gather Streams che preserva l'ordine (unendo):
A seconda dell'efficacia di runtime dell'ordinamento delle partizioni sul tuo hardware, questo potrebbe funzionare meglio di tutti e tre. Detto questo, le grandi modifiche non sono un'ottima idea nell'archivio delle colonne, quindi l'idea di cambiare partizione è quasi sicuramente migliore. Se riesci a far fronte ai lunghi tempi di compilazione e alle strane scelte del piano spesso viste con oggetti partizionati, specialmente quando il numero di partizioni è elevato.
La combinazione di molte funzionalità relativamente nuove, specialmente vicino ai loro limiti, è un ottimo modo per ottenere piani di esecuzione scadenti. La profondità del supporto dell'ottimizzatore tende a migliorare nel tempo, ma l'utilizzo di 15.000 partizioni del negozio di colonne probabilmente significherà sempre che vivrai in tempi interessanti.
OPTION (QUERYRULEOFF EnforceHPandAccCard)
la bobina scompare. Presumo che HP potrebbe essere "Protezione di Halloween". Tuttavia, quindi il tentativo di utilizzare quel piano con unUSE PLAN
suggerimento fallisce (così come il tentativo di utilizzare il piano anche dallaOPTIMIZE FOR
soluzione alternativa)