Una stima della cardinalità scadente squalifica INSERT dalla registrazione minima?


11

Perché la seconda INSERTfrase ~ 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 seconde genera 64MBi 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 SELECTun'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 secondse genera 461MBi 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).

Risposte:


7

Perché la seconda query non è idonea per la registrazione minima?

La registrazione minima è disponibile per la seconda query, ma il motore sceglie di non utilizzarla in fase di esecuzione.

Esiste una soglia minima al di INSERT...SELECTsotto della quale sceglie di non utilizzare le ottimizzazioni del caricamento di massa. La configurazione di un'operazione di set di righe in blocco comporta un costo e l'inserimento in blocco di poche righe non comporterebbe un utilizzo efficiente dello spazio.

Cosa si può fare per migliorare la situazione?

Utilizzare uno dei molti altri metodi (ad esempio SELECT INTO) che non ha questa soglia. In alternativa, potresti essere in grado di riscrivere la query di origine in qualche modo per aumentare il numero stimato di righe / pagine oltre la soglia per INSERT...SELECT.

Vedi anche la risposta di Geoff per ulteriori informazioni utili.


Trivia forse interessante: SET STATISTICS IO riporta letture logiche per la tabella di destinazione solo quando non vengono utilizzate le ottimizzazioni per il caricamento di massa .


5

Sono stato in grado di ricreare il problema con il mio banco prova:

USE test;

CREATE TABLE dbo.SourceGood
(
    SourceGoodID INT NOT NULL
        CONSTRAINT PK_SourceGood
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , SomeData VARCHAR(384) NOT NULL
);

CREATE TABLE dbo.SourceBad
(
    SourceBadID INT NOT NULL
        CONSTRAINT PK_SourceBad
        PRIMARY KEY CLUSTERED
        IDENTITY(-2147483647,1)
    , SomeData VARCHAR(384) NOT NULL
);

CREATE TABLE dbo.InsertTest
(
    SourceBadID INT NOT NULL
        CONSTRAINT PK_InsertTest
        PRIMARY KEY CLUSTERED
    , SomeData VARCHAR(384) NOT NULL
);
GO

INSERT INTO dbo.SourceGood WITH (TABLOCK) (SomeData) 
SELECT TOP(5000000) o.name + o1.name + o2.name
FROM syscolumns o
    , syscolumns o1
    , syscolumns o2;
GO

ALTER DATABASE test SET AUTO_UPDATE_STATISTICS OFF;
GO

INSERT INTO dbo.SourceBad WITH (TABLOCK) (SomeData)
SELECT TOP(5000000) o.name + o1.name + o2.name
FROM syscolumns o
    , syscolumns o1
    , syscolumns o2;
GO

ALTER DATABASE test SET AUTO_UPDATE_STATISTICS ON;
GO

BEGIN TRANSACTION;

INSERT INTO dbo.InsertTest WITH (TABLOCK)
SELECT *
FROM dbo.SourceGood;

SELECT * FROM sys.dm_tran_database_transactions;

/*
database_transaction_log_record_count
472 
database_transaction_log_bytes_used
692136
*/

COMMIT TRANSACTION;


BEGIN TRANSACTION;

INSERT INTO dbo.InsertTest WITH (TABLOCK)
SELECT *
FROM dbo.SourceBad;

SELECT * FROM sys.dm_tran_database_transactions;

/*
database_transaction_log_record_count   
5000003 
database_transaction_log_bytes_used
642699256
*/

COMMIT TRANSACTION;

Ciò pone la domanda, perché non "risolvere" il problema aggiornando le statistiche sulle tabelle di origine prima di eseguire l'operazione con registrazione minima?

TRUNCATE TABLE dbo.InsertTest;
UPDATE STATISTICS dbo.SourceBad;

BEGIN TRANSACTION;

INSERT INTO dbo.InsertTest WITH (TABLOCK)
SELECT *
FROM dbo.SourceBad;

SELECT * FROM sys.dm_tran_database_transactions;

/*
database_transaction_log_record_count
472
database_transaction_log_bytes_used
692136
*/

COMMIT TRANSACTION;

2
Nel codice reale, esiste SELECTun'istruzione complessa con numerosi join che genera il set di risultati per INSERT. Questi join producono stime di cardinalità scadenti per l'operatore di inserimento della tabella finale (che ho simulato nello script di repro tramite la cattiva UPDATE STATISTICSchiamata), e quindi non è così semplice come emettere un UPDATE STATISTICScomando 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.
Geoff Patterson,

Non ho un'istanza di SQL Server 2014 su cui testarlo, tuttavia Identificare i problemi di stima della nuova cardinalità di SQL Server 2014 e il miglioramento del Service Pack 1 parla dell'abilitazione del flag di traccia 4199, tra gli altri, per abilitare la nuova stima della cardinalità. Ci hai provato?
Max Vernon,

Buona idea, ma non ha aiutato. Ho appena provato TF 4199, TF 610 (allenta le condizioni di registrazione minime) ed entrambi insieme (ehi, perché no?), Ma nessuna modifica per la seconda query di prova.
Geoff Patterson,

4

Riscrivi la query di origine in qualche modo per aumentare il numero stimato di righe

Espandendo l'idea di Paul, una soluzione alternativa se si è davvero alla disperata ricerca è quella di aggiungere una tabella fittizia che garantisca che il numero stimato di righe per l'inserto sia sufficientemente elevato da garantire la qualità per le ottimizzazioni del caricamento di massa. Ho confermato che questo ha una registrazione minima e migliora le prestazioni delle query.

-- Create a dummy table that SQL Server thinks has a million rows
CREATE TABLE dbo.emptyTableWithMillionRowEstimate (
    n INT PRIMARY KEY
)
GO
UPDATE STATISTICS dbo.emptyTableWithMillionRowEstimate
WITH ROWCOUNT = 1000000
GO

-- Concatenate this table into the final rowset:
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.fiveMillionNumbersBadEstimate
-- Add in dummy rowset to ensure row estimate is high enough for bulk load optimization
UNION ALL
SELECT NULL FROM dbo.emptyTableWithMillionRowEstimate
OPTION (MAXDOP 1)

Asporto finale

  1. Utilizzare SELECT...INTOper operazioni di inserimento singolo se è richiesta una registrazione minima. Come sottolinea Paul, ciò garantirà una registrazione minima indipendentemente dalla stima della riga
  2. Ove possibile, scrivere query in modo semplice su cui Query Optimizer possa ragionare in modo efficace. Potrebbe essere possibile suddividere una query in più pezzi, ad esempio, al fine di consentire la creazione di statistiche su una tabella intermedia.
  3. Se hai accesso a SQL Server 2014, provalo sulla tua query; nel mio caso di produzione, l'ho appena provato e il nuovo stimatore della cardinalità ha prodotto una stima molto più elevata (e migliore); la query è stata quindi minimamente registrata. Ma questo potrebbe non essere utile se è necessario supportare SQL 2012 e versioni precedenti.
  4. Se sei disperato, potrebbero essere applicate soluzioni confuse come questa!

Un articolo correlato

Il post sul blog di Paul White di maggio 2019 Registrazione minima con INSERT ... SELECT in Heap Tables copre alcune di queste informazioni in modo più dettagliato.

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.