Perché la seconda INSERT
frase ~ 5 volte più lenta della prima?
Dalla quantità di dati di registro generati, penso che il secondo non sia idoneo per la registrazione minima. Tuttavia, la documentazione nella Guida alle prestazioni del caricamento dei dati indica che entrambi gli inserti dovrebbero essere in grado di essere minimamente registrati. Quindi, se la registrazione minima è la differenza di prestazioni chiave, perché la seconda query non si qualifica per la registrazione minima? Cosa si può fare per migliorare la situazione?
Query n. 1: inserimento di righe 5MM utilizzando INSERT ... WITH (TABLOCK)
Si consideri la seguente query, che inserisce le righe 5MM in un heap. Questa query viene eseguita 1 second
e genera 64MB
i dati del registro delle transazioni come riportato da sys.dm_tran_database_transactions
.
CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that correctly estimates that it will generate 5MM rows
FROM dbo.fiveMillionNumbers
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO
Query n. 2: inserimento degli stessi dati, ma SQL sottovaluta il numero di righe
Consideriamo ora questa query molto simile, che opera esattamente sugli stessi dati, ma sembra trarre da una tabella (o SELECT
un'istruzione complessa con molti join nel mio caso di produzione reale) in cui la stima della cardinalità è troppo bassa. Questa query viene eseguita 5.5 seconds
e genera 461MB
i dati del registro delle transazioni.
CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that produces 5MM rows but SQL estimates just 1000 rows
FROM dbo.fiveMillionNumbersBadEstimate
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO
Sceneggiatura completa
Vedi questo Pastebin per un set completo di script per generare i dati di test ed eseguire uno di questi scenari. Si noti che è necessario utilizzare un database che si trova nel SIMPLE
modello di recupero .
Contesto aziendale
Stiamo spostando semi-frequentemente milioni di righe di dati ed è importante che queste operazioni siano il più efficienti possibile, sia in termini di tempo di esecuzione che di carico di I / O del disco. Inizialmente avevamo avuto l'impressione che la creazione di una tabella heap e l'utilizzo INSERT...WITH (TABLOCK)
fosse un buon modo per farlo, ma ora siamo diventati meno sicuri dato che abbiamo osservato la situazione sopra dimostrata in uno scenario di produzione reale (anche se con query più complesse, non il versione semplificata qui).
SELECT
un'istruzione complessa con numerosi join che genera il set di risultati perINSERT
. Questi join producono stime di cardinalità scadenti per l'operatore di inserimento della tabella finale (che ho simulato nello script di repro tramite la cattivaUPDATE STATISTICS
chiamata), e quindi non è così semplice come emettere unUPDATE STATISTICS
comando per risolvere il problema. Sono completamente d'accordo sul fatto che semplificare la query in modo che sia più semplice da comprendere per lo stimatore della cardinalità potrebbe essere un buon approccio, ma non è una fase di tralcio implementare una logica aziendale complessa.