Perché una tabella temporanea è una soluzione più efficiente al problema di Halloween di una bobina desiderosa?


14

Considera la seguente query che inserisce le righe da una tabella di origine solo se non sono già nella tabella di destinazione:

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT maybe_new_rows.ID
FROM dbo.A_HEAP_OF_MOSTLY_NEW_ROWS maybe_new_rows
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR halloween
    WHERE maybe_new_rows.ID = halloween.ID
)
OPTION (MAXDOP 1, QUERYTRACEON 7470);

Una possibile forma del piano include un join unione e una bobina desiderosa. L'operatore desideroso di spool è presente per risolvere il problema di Halloween :

primo piano

Sulla mia macchina, il codice sopra riportato viene eseguito in circa 6900 ms. Il codice di riproduzione per creare le tabelle è incluso nella parte inferiore della domanda. Se non sono soddisfatto delle prestazioni, potrei provare a caricare le righe da inserire in una tabella temporanea invece di fare affidamento sul rocchetto desideroso. Ecco una possibile implementazione:

DROP TABLE IF EXISTS #CONSULTANT_RECOMMENDED_TEMP_TABLE;
CREATE TABLE #CONSULTANT_RECOMMENDED_TEMP_TABLE (
    ID BIGINT,
    PRIMARY KEY (ID)
);

INSERT INTO #CONSULTANT_RECOMMENDED_TEMP_TABLE WITH (TABLOCK)
SELECT maybe_new_rows.ID
FROM dbo.A_HEAP_OF_MOSTLY_NEW_ROWS maybe_new_rows
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR halloween
    WHERE maybe_new_rows.ID = halloween.ID
)
OPTION (MAXDOP 1, QUERYTRACEON 7470);

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT new_rows.ID
FROM #CONSULTANT_RECOMMENDED_TEMP_TABLE new_rows
OPTION (MAXDOP 1);

Il nuovo codice viene eseguito in circa 4400 ms. Sono in grado di ottenere piani reali e utilizzare Actual Time Statistics ™ per esaminare dove viene trascorso il tempo a livello di operatore. Si noti che la richiesta di un piano effettivo aggiunge un notevole sovraccarico per queste query in modo che i totali non corrispondano ai risultati precedenti.

╔═════════════╦═════════════╦══════════════╗
  operator    first query  second query 
╠═════════════╬═════════════╬══════════════╣
 big scan     1771         1744         
 little scan  163          166          
 sort         531          530          
 merge join   709          669          
 spool        3202         N/A          
 temp insert  N/A          422          
 temp scan    N/A          187          
 insert       3122         1545         
╚═════════════╩═════════════╩══════════════╝

Il piano di query con lo spooler desideroso sembra impiegare molto più tempo sugli operatori di inserimento e spool rispetto al piano che utilizza la tabella temporanea.

Perché il piano con la tabella temporanea è più efficiente? Una bobina desiderosa non è comunque solo una tabella temporanea interna? Credo di cercare risposte incentrate sugli interni. Sono in grado di vedere come le pile di chiamate sono diverse ma non riesco a capire il quadro generale.

Sono su SQL Server 2017 CU 11 nel caso qualcuno volesse saperlo. Ecco il codice per popolare le tabelle utilizzate nelle query precedenti:

DROP TABLE IF EXISTS dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR;

CREATE TABLE dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR (
ID BIGINT NOT NULL,
PRIMARY KEY (ID)
);

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT TOP (20000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
CROSS JOIN master..spt_values t3
OPTION (MAXDOP 1);


DROP TABLE IF EXISTS dbo.A_HEAP_OF_MOSTLY_NEW_ROWS;

CREATE TABLE dbo.A_HEAP_OF_MOSTLY_NEW_ROWS (
ID BIGINT NOT NULL
);

INSERT INTO dbo.A_HEAP_OF_MOSTLY_NEW_ROWS WITH (TABLOCK)
SELECT TOP (1900000) 19999999 + ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

Risposte:


14

Questo è ciò che chiamo Protezione manuale di Halloween .

Puoi trovare un esempio di come viene utilizzato con un'istruzione di aggiornamento nel mio articolo Ottimizzazione delle query di aggiornamento . Bisogna stare un po 'attenti a preservare la stessa semantica, ad esempio bloccando la tabella di destinazione contro tutte le modifiche simultanee mentre vengono eseguite le query separate, se questo è rilevante nel proprio scenario.

Perché il piano con la tabella temporanea è più efficiente? Una bobina desiderosa non è comunque solo una tabella temporanea interna?

Uno spool ha alcune delle caratteristiche di una tabella temporanea, ma i due non sono equivalenti esatti. In particolare, uno spool è essenzialmente un inserto non ordinato riga per riga in una struttura b-tree . Trae vantaggio dal blocco e dalla registrazione delle ottimizzazioni, ma non supporta le ottimizzazioni del carico di massa .

Di conseguenza, è spesso possibile ottenere prestazioni migliori suddividendo la query in modo naturale: caricamento in blocco delle nuove righe in una tabella o variabile temporanea, quindi esecuzione di un inserimento ottimizzato (senza protezione esplicita di Halloween) dall'oggetto temporaneo.

Effettuare questa separazione consente inoltre una maggiore libertà di ottimizzare separatamente le parti di lettura e scrittura dell'istruzione originale.

Come nota a margine, è interessante pensare a come il problema di Halloween potrebbe essere affrontato usando le versioni di riga. Forse una versione futura di SQL Server fornirà tale funzionalità in circostanze adeguate.


Come ha accennato Michael Kutz in un commento, potresti anche esplorare la possibilità di sfruttare l' ottimizzazione del riempimento dei fori per evitare HP espliciti. Un modo per raggiungere questo obiettivo per la demo è quello di creare un indice univoco (raggruppato se vuoi) nella IDcolonna di A_HEAP_OF_MOSTLY_NEW_ROWS.

CREATE UNIQUE INDEX i ON dbo.A_HEAP_OF_MOSTLY_NEW_ROWS (ID);

Con tale garanzia in atto, l'ottimizzatore può utilizzare il riempimento dei fori e la condivisione del set di righe:

MERGE dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (SERIALIZABLE) AS HICETY
USING dbo.A_HEAP_OF_MOSTLY_NEW_ROWS AS AHOMNR
    ON AHOMNR.ID = HICETY.ID
WHEN NOT MATCHED BY TARGET
THEN INSERT (ID) VALUES (AHOMNR.ID);

Piano MERGE

Sebbene interessante, sarai comunque in grado di ottenere prestazioni migliori in molti casi utilizzando la Protezione manuale di Halloween accuratamente implementata.


5

Per espandere un po 'la risposta di Paul, parte della differenza nel tempo trascorso tra gli approcci di spool e temp table sembra dipendere dalla mancanza di supporto per l' DML Request Sortopzione nel piano di spool. Con flag di traccia non documentato 8795, il tempo trascorso per l'approccio della tabella temporanea passa da 4400 ms a 5600 ms.

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT new_rows.ID
FROM #CONSULTANT_RECOMMENDED_TEMP_TABLE new_rows
OPTION (MAXDOP 1, QUERYTRACEON 8795);

Si noti che questo non è esattamente equivalente all'inserto eseguito dal piano di spool. Questa query scrive significativamente più dati nel registro delle transazioni.

Lo stesso effetto può essere visto al contrario con qualche trucco. È possibile incoraggiare SQL Server a utilizzare un ordinamento anziché uno spool per la protezione di Halloween. Un'implementazione:

INSERT INTO dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR WITH (TABLOCK)
SELECT TOP (987654321) 
maybe_new_rows.ID
FROM dbo.A_HEAP_OF_MOSTLY_NEW_ROWS maybe_new_rows
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.HALLOWEEN_IS_COMING_EARLY_THIS_YEAR halloween
    WHERE maybe_new_rows.ID = halloween.ID
)
ORDER BY maybe_new_rows.ID, maybe_new_rows.ID + 1
OPTION (MAXDOP 1, QUERYTRACEON 7470, MERGE JOIN);

Ora il piano ha un operatore TOP N Sort al posto della bobina. L'ordinamento è un operatore di blocco, quindi la bobina non è più necessaria:

inserisci qui la descrizione dell'immagine

Ancora più importante, ora abbiamo il supporto per DML Request Sort opzione. Guardando di nuovo le statistiche del tempo reale, l'operatore di inserimento ora richiede solo 1623 ms. L'intero piano richiede circa 5400 ms per l'esecuzione senza richiedere un piano effettivo.

Come spiega Hugo , l'operatore Eager Spool mantiene l'ordine. Ciò può essere facilmente visto con un TOP PERCENTpiano. È un peccato che la query originale con lo spool non possa sfruttare meglio la natura ordinata dei dati nello spool.

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.