INSERISCI a riga singola ... SELEZIONA molto più lentamente di SELEZIONA separata


18

Data la seguente tabella heap con 400 righe numerate da 1 a 400:

DROP TABLE IF EXISTS dbo.N;
GO
SELECT 
    SV.number
INTO dbo.N 
FROM master.dbo.spt_values AS SV
WHERE 
    SV.[type] = N'P'
    AND SV.number BETWEEN 1 AND 400;

e le seguenti impostazioni:

SET NOCOUNT ON;
SET STATISTICS IO, TIME OFF;
SET STATISTICS XML OFF;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

La seguente SELECTdichiarazione si completa in circa 6 secondi ( demo , piano ):

DECLARE @n integer = 400;

SELECT
    c = COUNT_BIG(*) 
FROM dbo.N AS N
CROSS JOIN dbo.N AS N2
CROSS JOIN dbo.N AS N3
WHERE 
    N.number <= @n
    AND N2.number <= @n
    AND N3.number <= @n
OPTION
    (OPTIMIZE FOR (@n = 1));

Nota: la OPTIMIZE FORclausola @ è solo per produrre una riproduzione di dimensioni ragionevoli che catturi i dettagli essenziali del problema reale, incluso un errore di cardinalità che può sorgere per una serie di motivi.

Quando l'output a riga singola viene scritto in una tabella, sono necessari 19 secondi ( demo , piano ):

DECLARE @T table (c bigint NOT NULL);

DECLARE @n integer = 400;

INSERT @T
    (c)
SELECT
    c = COUNT_BIG(*) 
FROM dbo.N AS N
CROSS JOIN dbo.N AS N2
CROSS JOIN dbo.N AS N3
WHERE 
    N.number <= @n
    AND N2.number <= @n
    AND N3.number <= @n
OPTION
    (OPTIMIZE FOR (@n = 1));

I piani di esecuzione appaiono identici a parte l'inserimento di una riga.

Tutto il tempo extra sembra essere consumato dall'uso della CPU.

Perché l' INSERTaffermazione è molto più lenta?

Risposte:


21

SQL Server sceglie di eseguire la scansione delle tabelle di heap sul lato interno dei join di loop utilizzando i blocchi a livello di riga. Una scansione completa normalmente sceglierebbe il blocco a livello di pagina, ma una combinazione della dimensione della tabella e del predicato indica che il motore di archiviazione sceglie i blocchi di riga, poiché questa sembra essere la strategia più economica.

La misestimazione della cardinalità è stata deliberatamente introdotta con il OPTIMIZE FORmodo in cui gli heap vengono scansionati molte più volte di quanto l'ottimizzatore si aspetti e non introduce una bobina come farebbe normalmente.

Questa combinazione di fattori indica che le prestazioni sono molto sensibili al numero di blocchi richiesti in fase di esecuzione.

L' SELECTistruzione beneficia di un'ottimizzazione che consente di saltare i blocchi condivisi a livello di riga (prendendo solo i blocchi a livello di pagina condivisi intenzionalmente) quando non vi è pericolo di leggere dati non impegnati e non ci sono dati fuori riga.

La INSERT...SELECTdichiarazione non beneficia di questa ottimizzazione, quindi milioni di blocchi RID vengono acquisiti e rilasciati ogni secondo nel secondo caso, insieme ai blocchi a livello di pagina condivisi intenzionalmente.

L'enorme quantità di attività di blocco rappresenta la CPU aggiuntiva e il tempo trascorso.

La soluzione più naturale è garantire che l'ottimizzatore (e il motore di archiviazione) ottengano stime di cardinalità decenti in modo che possano fare delle buone scelte.

Se ciò non è pratico nel caso d'uso reale, le istruzioni INSERTe SELECTpotrebbero essere separate, con il risultato del SELECTtrattenuto in una variabile. Ciò consentirà SELECTall'istruzione di beneficiare dell'ottimizzazione del blocco dei blocchi.

La modifica del livello di isolamento può anche essere fatta funzionare, non prendendo blocchi condivisi o assicurando che l'escalation dei blocchi avvenga rapidamente.

Come ultimo punto di interesse, la query può essere eseguita in modo ancora più veloce rispetto al SELECTcaso ottimizzato forzando l'uso di spool utilizzando il flag di traccia non documentato 8691.

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.