L'operatore spool desideroso è utile per questa eliminazione da un archivio di colonne in cluster?


28

Sto testando l'eliminazione dei dati da un indice columnstore cluster.

Ho notato che nel piano di esecuzione è presente un grande operatore di spooler desideroso:

inserisci qui la descrizione dell'immagine

Questo si completa con le seguenti caratteristiche:

  • 60 milioni di righe cancellate
  • 1.9 GiB TempDB utilizzato
  • 14 minuti di tempo di esecuzione
  • Piano seriale
  • 1 rebind su bobina
  • Costo stimato per la scansione: 364.821

Se induco lo stimatore a sottovalutare, ottengo un piano più veloce che evita l'uso di TempDB:

inserisci qui la descrizione dell'immagine

Costo stimato della scansione: 56.901

(Questo è un piano stimato, ma i numeri nei commenti sono corretti.)

È interessante notare che la bobina scompare di nuovo se svuoto i negozi delta eseguendo quanto segue:

ALTER INDEX IX_Clustered ON Fact.RecordedMetricsDetail REORGANIZE WITH (COMPRESS_ALL_ROW_GROUPS = ON);

Lo spool sembra essere introdotto solo quando ci sono più di alcune soglie di pagine negli archivi delta.

Per verificare la dimensione dei negozi delta, sto eseguendo la seguente query per verificare la presenza di pagine in fila per la tabella:

SELECT  
  SUM([in_row_used_page_count]) AS in_row_used_pages,
  SUM(in_row_data_page_count) AS in_row_data_pages
FROM sys.[dm_db_partition_stats] as pstats
JOIN sys.partitions AS p
ON pstats.partition_id = p.partition_id
WHERE p.[object_id] = OBJECT_ID('Fact.RecordedMetricsDetail');

C'è qualche plausibile vantaggio per l'iteratore di spool nel primo piano? Devo presumere che sia inteso come un miglioramento delle prestazioni e non per la protezione di Halloween perché la sua presenza non è coerente.

Lo sto testando su CTP 3.1 2016, ma vedo lo stesso comportamento su CU3 2014 SP1.

Ho pubblicato uno script che genera schemi e dati e ti guida attraverso la dimostrazione del problema qui .

La domanda è principalmente per curiosità sul comportamento dell'ottimizzatore a questo punto poiché ho una soluzione alternativa per il problema che ha portato alla domanda (un grande TempDB riempito di spool). Ora sto eliminando utilizzando invece il cambio di partizione.


2
Se provo OPTION (QUERYRULEOFF EnforceHPandAccCard)la bobina scompare. Presumo che HP potrebbe essere "Protezione di Halloween". Tuttavia, quindi il tentativo di utilizzare quel piano con un USE PLANsuggerimento fallisce (così come il tentativo di utilizzare il piano anche dalla OPTIMIZE FOR soluzione alternativa)
Martin Smith,

Grazie @MartinSmith. Qualche idea di cosa AccCardsarebbe? Colonna ascendente cardinalità cardinalità forse?
James L,

1
@JamesLupolt No, non sono riuscito a trovare qualcosa di particolarmente convincente per me. Forse l'acc è accumulare o accedere?
Martin Smith,

Risposte:


22

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à :

Piano originale

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 DMLRequestSortproprietà 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à:

DMLRequestSort = piano vero

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 PLANsuggerimento 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):

Elimina ordinato

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.

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.