Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Quando SQL Server ordina il riavvolgimento?

Introduzione

Riavvolgi sono specifici per gli operatori sul lato interno di un join o applicato di loop nidificati. L'idea è di riutilizzare i risultati calcolati in precedenza da parte di un piano di esecuzione dove è sicuro farlo.

L'esempio canonico di un operatore di piano che può riavvolgere è il pigro Spool di tabella . La sua ragione d'essere consiste nel memorizzare nella cache le righe dei risultati da una sottostruttura del piano, quindi riprodurre quelle righe nelle iterazioni successive se i parametri del ciclo correlati sono invariati. La riproduzione delle righe può essere più economica rispetto alla riesecuzione del sottoalbero che le ha generate. Per ulteriori informazioni su questi spool di prestazioni vedi il mio articolo precedente.

La documentazione dice che solo i seguenti operatori possono riavvolgere:

  • Spool da tavolo
  • Spool conteggio righe
  • Spool di indici non cluster
  • Funzione con valori di tabella
  • Ordina
  • Interrogazione remota
  • Afferma e Filtro operatori con un'Espressione di avvio

I primi tre elementi sono più spesso bobine di prestazioni, sebbene possano essere introdotte per altri motivi (quando possono essere desiderosi o pigri).

Funzioni con valori di tabella utilizzare una variabile di tabella, che può essere utilizzata per memorizzare nella cache e riprodurre i risultati in circostanze adeguate. Se sei interessato a riavvolgere le funzioni con valori di tabella, consulta le mie domande e risposte su Database Administrators Stack Exchange.

Con quelli fuori mano, questo articolo riguarda esclusivamente Ordini e quando possono riavvolgere.

Ordina riavvolgi

Ordina lo spazio di archiviazione (memoria e forse disco se si rovesciano) quindi hanno una struttura capace di memorizzazione di righe tra le iterazioni del ciclo. In particolare, l'output ordinato può, in linea di principio, essere riprodotto (riavvolto).

Tuttavia, la risposta breve alla domanda del titolo, "Do Sorts Rewind?" è:

Sì, ma non lo vedrai molto spesso.

Tipi di ordinamento

Gli ordinamenti sono di molti tipi diversi internamente, ma per i nostri scopi attuali ce ne sono solo due:

  1. Ordinamento in memoria (CQScanInMemSortNew ).
    • Sempre in memoria; non può versare su disco.
    • Utilizza l'ordinamento rapido della libreria standard.
    • Massimo 500 righe e due pagine da 8 KB in totale.
    • Tutti gli input devono essere costanti di runtime. In genere questo significa che l'intero sottoalbero di ordinamento deve essere composto da solo Scansione costante e/o Calcola scalare operatori.
    • Esplicitamente distinguibile nei piani di esecuzione solo quando showplan dettagliato è abilitato (flag di traccia 8666). Questo aggiunge proprietà extra a Ordina operatore, uno dei quali è “InMemory=[0|1]”.
  2. Tutti gli altri tipi.

(Entrambi i tipi di Ordina l'operatore include il suo Ordinamento Top N e Ordinamento distinto varianti).

Riavvolgi comportamenti

  • Ordinamenti in memoria puoi sempre riavvolgere quando è sicuro. Se non sono presenti parametri di loop correlati o se i valori dei parametri non sono cambiati rispetto all'iterazione immediatamente precedente, questo tipo di ordinamento può riprodurre i dati archiviati invece di rieseguire gli operatori al di sotto di essi nel piano di esecuzione.

  • Ordinamenti non in memoria può riavvolgere quando è sicuro, ma solo se Ordina l'operatore contiene al massimo una riga . Si prega di notare un Ordinamento input può fornire una riga su alcune iterazioni, ma non su altre. Il comportamento di runtime può quindi essere una complessa combinazione di riavvolgimenti e rilegature. Dipende completamente da quante righe sono fornite a Ordina su ogni iterazione in fase di esecuzione. In genere non è possibile prevedere cosa sia un Ordinamento farà su ogni iterazione ispezionando il piano di esecuzione.

La parola "sicuro" nelle descrizioni precedenti significa:Non si è verificata una modifica nel parametro o nessun operatore al di sotto di Ordina hanno una dipendenza dal valore modificato.

Nota importante sui piani di esecuzione

I piani di esecuzione non riportano sempre correttamente i rewind (e i rebind) per Ordina operatori. L'operatore segnala un rewind se alcuni parametri correlati sono invariati e un rebind in caso contrario.

Per gli ordinamenti non in memoria (di gran lunga i più comuni), un riavvolgimento segnalato riprodurrà effettivamente i risultati di ordinamento archiviati solo se è presente al massimo una riga nel buffer di output dell'ordinamento. In caso contrario, l'ordinamento segnala un rewind, ma il sottoalbero sarà ancora completamente rieseguito (una rilegatura).

Per verificare quanti riavvolgimenti segnalati erano riavvolgimenti effettivi, controlla il Numero di esecuzioni proprietà sugli operatori sotto Ordina .

Cronologia e la mia spiegazione

Ordina il comportamento di riavvolgimento dell'operatore può sembrare strano, ma è stato così da (almeno) SQL Server 2000 a SQL Server 2019 incluso (oltre al database SQL di Azure). Non sono stato in grado di trovare alcuna spiegazione ufficiale o documentazione a riguardo.

La mia opinione personale è che Ordina i riavvolgimento sono piuttosto costosi a causa dei macchinari di smistamento sottostanti, comprese le strutture per gli sversamenti, e l'uso delle transazioni di sistema in tempdb .

Nella maggior parte dei casi, l'ottimizzatore farà meglio a introdurre uno spooling delle prestazioni esplicito quando rileva la possibilità di duplicare i parametri di loop correlati. Gli spool sono il modo meno costoso per memorizzare nella cache i risultati parziali.

È possibile che riproduce un Ordina risultato sarebbe solo più conveniente rispetto a uno Spool quando Ordina contiene al massimo una riga. Dopotutto, l'ordinamento di una riga (o nessuna riga!) in realtà non implica alcun ordinamento, quindi gran parte del sovraccarico può essere evitato.

Pura speculazione, ma qualcuno doveva chiederlo, quindi eccola.

Demo 1:riavvolgi imprecisi

Questo primo esempio presenta due variabili di tabella. Il primo contiene tre valori duplicati tre volte nella colonna c1 . La seconda tabella contiene due righe per ogni corrispondenza su c2 = c1 . Le due righe corrispondenti sono contraddistinte da un valore nella colonna c3 .

Il compito è restituire la riga della seconda tabella con il c3 più alto valore per ogni corrispondenza su c1 = c2 . Il codice è probabilmente più chiaro della mia spiegazione:

DECLARE @T1 table (c1 integer NOT NULL INDEX i);
DECLARE @T2 table (c2 integer NOT NULL, c3 integer NOT NULL);
 
INSERT @T1
    (c1)
VALUES
    (1), (1), (1),
    (2), (2), (2),
    (3), (3), (3);
 
INSERT @T2 
    (c2, c3)
VALUES
    (1, 1),
    (1, 2),
    (2, 3),
    (2, 4),
    (3, 5),
    (3, 6);
 
SELECT
    T1.c1,
    CA.c2,
    CA.c3
FROM @T1 AS T1
CROSS APPLY
(
    SELECT TOP (1)
        T2.c2,
        T2.c3
    FROM @T2 AS T2
    WHERE 
        T2.c2 = T1.c1
    ORDER BY 
        T2.c3 DESC
) AS CA
ORDER BY T1.c1 ASC
OPTION (NO_PERFORMANCE_SPOOL);

Il NO_PERFORMANCE_SPOOL il suggerimento è lì per impedire all'ottimizzatore di introdurre una bobina di prestazioni. Questo può accadere con le variabili di tabella quando ad es. il flag di traccia 2453 è abilitato o è disponibile la compilazione differita della variabile di tabella, in modo che l'ottimizzatore possa vedere la vera cardinalità della variabile di tabella (ma non la distribuzione del valore).

I risultati della query mostrano il c2 e c3 i valori restituiti sono gli stessi per ogni c1 distinto valore:

Il piano di esecuzione effettivo della query è:

Il c1 i valori, presentati in ordine, corrispondono all'iterazione precedente 6 volte e cambiano 3 volte. Ordina segnala questo come 6 rewind e 3 rebind.

Se ciò fosse vero, la Scansione tabella verrebbe eseguito solo 3 volte. Ordina riprodurrebbe (riavvolgerebbe) i suoi risultati nelle altre 6 occasioni.

Così com'è, possiamo vedere la Scansione tabella è stato eseguito 9 volte, una per ogni riga della tabella @T1 . Non si sono verificati riavvolgimenti qui .

Demo 2:Ordina riavvolgi

L'esempio precedente non consentiva Ordina riavvolgere perché (a) non è un ordinamento in memoria; e (b) su ogni iterazione del ciclo, Ordina conteneva due righe. Plan Explorer mostra un totale di 18 righe dalla Scansione tabella , due righe su ciascuna delle 9 iterazioni.

Modifichiamo l'esempio ora in modo che ce ne sia solo uno riga nella tabella @T2 per ogni riga corrispondente da @T1 :

DECLARE @T1 table (c1 integer NOT NULL INDEX i);
DECLARE @T2 table (c2 integer NOT NULL, c3 integer NOT NULL);
 
INSERT @T1
    (c1)
VALUES
    (1), (1), (1),
    (2), (2), (2),
    (3), (3), (3);
 
-- Only one matching row per iteration now
INSERT @T2 
    (c2, c3)
VALUES
    --(1, 1),
    (1, 2),
    --(2, 3),
    (2, 4),
    --(3, 5),
    (3, 6);
 
SELECT
    T1.c1,
    CA.c2,
    CA.c3
FROM @T1 AS T1
CROSS APPLY
(
    SELECT TOP (1)
        T2.c2,
        T2.c3
    FROM @T2 AS T2
    WHERE 
        T2.c2 = T1.c1
    ORDER BY 
        T2.c3 DESC
) AS CA
ORDER BY T1.c1 ASC
OPTION (NO_PERFORMANCE_SPOOL);

I risultati sono gli stessi mostrati in precedenza perché abbiamo mantenuto la riga corrispondente che è stata ordinata più in alto nella colonna c3 . Anche il piano di esecuzione è superficialmente simile, ma con una differenza importante:

Con una riga in Ordina in qualsiasi momento è in grado di riavvolgere quando il parametro correlato c1 non cambia. La Scansione tabella come risultato viene eseguito solo 3 volte.

Nota l'Ordina produce più righe (9) di quanto riceve (3). Questa è una buona indicazione che un Ordina è riuscito a memorizzare nella cache un set di risultati una o più volte:un riavvolgimento riuscito.

Demo 3:riavvolgere nulla

Prima ho menzionato un Ordina non in memoria può riavvolgere quando contiene al massimo una riga.

Per vederlo in azione con zero righe , cambiamo in un OUTER APPLY e non inserire righe nella tabella @T2 . Per ragioni che diventeranno evidenti a breve, smetteremo anche di proiettare la colonna c2 :

DECLARE @T1 table (c1 integer NOT NULL INDEX i);
DECLARE @T2 table (c2 integer NOT NULL, c3 integer NOT NULL);
 
INSERT @T1
    (c1)
VALUES
    (1), (1), (1),
    (2), (2), (2),
    (3), (3), (3);
 
-- No rows added to table @T2
 
-- No longer projecting c2
SELECT
    T1.c1,
    --CA.c2,
    CA.c3
FROM @T1 AS T1
OUTER APPLY
(
    SELECT TOP (1)
        --T2.c2,
        T2.c3
    FROM @T2 AS T2
    WHERE 
        T2.c2 = T1.c1
    ORDER BY 
        T2.c3 DESC
) AS CA
ORDER BY T1.c1 ASC
OPTION (NO_PERFORMANCE_SPOOL);

I risultati ora hanno NULL nella colonna c3 come previsto:

Il piano di esecuzione è:

Ordina è stato in grado di riavvolgere senza righe nel buffer, quindi Scansione tabella è stato eseguito solo 3 volte, ogni volta nella colonna c1 valore modificato.

Demo 4:Rewind massimo!

Come gli altri operatori che supportano il riavvolgimento, un Ordina si limiterà a rilegare il suo sottoalbero se un parametro correlato è cambiato e il sottoalbero dipende in qualche modo da quel valore.

Ripristino della colonna c2 la proiezione alla demo 3 mostrerà questo in azione:

DECLARE @T1 table (c1 integer NOT NULL INDEX i);
DECLARE @T2 table (c2 integer NOT NULL, c3 integer NOT NULL);
 
INSERT @T1
    (c1)
VALUES
    (1), (1), (1),
    (2), (2), (2),
    (3), (3), (3);
 
-- Still no rows in @T2
-- Column c2 is back!
SELECT
    T1.c1,
    CA.c2,
    CA.c3
FROM @T1 AS T1
OUTER APPLY
(
    SELECT TOP (1)
        T2.c2,
        T2.c3
    FROM @T2 AS T2
    WHERE 
        T2.c2 = T1.c1
    ORDER BY 
        T2.c3 DESC
) AS CA
ORDER BY T1.c1 ASC
OPTION (NO_PERFORMANCE_SPOOL);

I risultati ora mostrano due NULL colonne ovviamente:

Il piano di esecuzione è molto diverso:

Questa volta, il Filtro contiene il controllo T2.c2 = T1.c1 , effettuando la Scansione tabella indipendente del valore corrente del parametro correlato c1 . Ordina può riavvolgere in sicurezza 8 volte, il che significa che la scansione viene eseguita solo una volta .

Demo 5:ordinamento in memoria

L'esempio successivo mostra un Ordina in memoria operatore:

DECLARE @T table (v integer NOT NULL);
 
INSERT @T 
    (v)
VALUES 
    (1), (2), (3), 
    (4), (5), (6);
 
SELECT 
    T.v,
    OA.i 
FROM @T AS T
OUTER APPLY
(
    SELECT TOP (1) 
        X.i 
    FROM 
    (
        VALUES
            (REPLICATE('Z', 1390)),
            ('0'), ('1'), ('2'), ('3'), ('4'), 
            ('5'), ('6'), ('7'), ('8'), ('9')
    ) AS X (i)
    ORDER BY NEWID()
) AS OA
OPTION (NO_PERFORMANCE_SPOOL);

I risultati che otterrai varieranno da un'esecuzione all'altra, ma ecco un esempio:

La cosa interessante sono i valori nella colonna i sarà sempre lo stesso, nonostante ORDER BY NEWID() clausola.

Probabilmente avrai già intuito che il motivo è l'Ordina risultati di memorizzazione nella cache (riavvolgimento). Il piano di esecuzione mostra la Scansione Costante eseguendo una sola volta, producendo 11 righe in totale:

Questo ordinamento ha solo Compute Scalar e Scansione costante operatori sul suo input, quindi è un Ordinamento in memoria . Ricorda, questi non sono limitati al massimo a una singola riga:possono contenere 500 righe e 16 KB.

Come accennato in precedenza, non è possibile vedere esplicitamente se un Ordina è In-Memoria o meno ispezionando un piano di esecuzione regolare. Abbiamo bisogno di un output dettagliato dello showplan , abilitato con flag di traccia non documentato 8666. Con quello abilitato, vengono visualizzate ulteriori proprietà dell'operatore:

Quando non è pratico utilizzare flag di traccia non documentati, puoi dedurre che un Ordina è "InMemory" dalla sua Frazione di memoria di input essendo zero e Utilizzo della memoria elementi non disponibili in showplan post-esecuzione (nelle versioni di SQL Server che supportano tali informazioni).

Torna al piano di esecuzione:non ci sono parametri correlati, quindi Ordina è libero di riavvolgere 5 volte, ovvero la Scansione Costante viene eseguito solo una volta. Sentiti libero di cambiare il TOP (1) a TOP (3) o qualunque cosa ti piaccia. Il riavvolgimento significa che i risultati saranno gli stessi (memorizzati nella cache/riavvolti) per ogni riga di input.

Potresti essere infastidito da ORDER BY NEWID() clausola che non impedisce il riavvolgimento. Questo è davvero un punto controverso, ma non è affatto limitato a sorta. Per una discussione più completa (avviso:possibile tana del coniglio), vedere queste domande e risposte. La versione breve è che si tratta di una decisione di progettazione del prodotto deliberata, che ottimizza le prestazioni, ma ci sono piani per rendere il comportamento più intuitivo nel tempo.

Demo 6:nessun ordinamento in memoria

È lo stesso della demo 5, tranne per il fatto che la stringa replicata è più lunga di un carattere:

DECLARE @T table (v integer NOT NULL);
 
INSERT @T 
    (v)
VALUES 
    (1), (2), (3), 
    (4), (5), (6);
 
SELECT 
    T.v,
    OA.i 
FROM @T AS T
OUTER APPLY
(
    SELECT TOP (1) 
        X.i 
    FROM 
    (
        VALUES
            -- 1391 instead of 1390
            (REPLICATE('Z', 1391)),
            ('0'), ('1'), ('2'), ('3'), ('4'), 
            ('5'), ('6'), ('7'), ('8'), ('9')
    ) AS X (i)
    ORDER BY NEWID()
) AS OA
OPTION (NO_PERFORMANCE_SPOOL);

Anche in questo caso, i risultati varieranno in base all'esecuzione, ma ecco un esempio. Nota la i i valori ora non sono tutti uguali:

Il carattere extra è appena sufficiente per spingere la dimensione stimata dei dati ordinati oltre 16 KB. Ciò significa un ordinamento in memoria non può essere utilizzato e il riavvolgimento scompare.

Il piano di esecuzione è:

Ordina ancora rapporti 5 riavvolge, ma la Scansione Costante viene eseguito 6 volte, il che significa che non si sono verificati realmente riavvolgimenti. Produce tutte le 11 righe su ciascuna delle 6 esecuzioni, per un totale di 66 righe.

Riepilogo e pensieri finali

Non vedrai un Ordina operatore veramente riavvolgendo molto spesso, anche se lo vedrai dire di sì parecchio.

Ricorda, un normale Ordinamento può riavvolgere solo se è sicuro e c'è un massimo di una riga nell'ordinamento alla volta. Essere "sicuro" significa non modificare i parametri di correlazione del ciclo o nulla al di sotto di Ordina è influenzato dalle modifiche ai parametri.

Un ordinamento in memoria può operare su un massimo di 500 righe e 16 KB di dati provenienti da Scansione costante e Computer scalare solo operatori. Si riavvolgerà anche solo quando è sicuro (bug del prodotto a parte!) ma non è limitato a un massimo di una riga.

Questi possono sembrare dettagli esoterici, e suppongo che lo siano. Detto questo, mi hanno aiutato a capire un piano di esecuzione e a trovare buoni miglioramenti delle prestazioni più di una volta. Forse un giorno troverai anche le informazioni utili.

Cerca Tipi che producono più righe di quante ne hanno nel loro input!

Se desideri vedere un esempio più realistico di Ordina riavvolge sulla base di una demo fornita da Itzik Ben-Gan nella prima parte della sua Partita più vicina serie, vedere Corrispondenza più vicina con ordina riavvolgi.