Puoi spiegare questo piano di esecuzione?


20

Stavo cercando qualcos'altro quando mi sono imbattuto in questa cosa. Stavo generando tabelle di test con alcuni dati al suo interno ed eseguendo query diverse per scoprire in che modo i diversi modi di scrivere query influiscono sul piano di esecuzione. Ecco lo script che ho usato per generare dati di test casuali:

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID('t') AND type in (N'U'))
DROP TABLE t
GO

CREATE TABLE t 
(
 c1 int IDENTITY(1,1) NOT NULL 
,c2 int NULL
) 
GO

insert into t
select top 1000000 a from
(select t1.number*2048 + t2.number a, newid() b
from [master]..spt_values t1 
cross join  [master]..spt_values t2
where t1.[type] = 'P' and t2.[type] = 'P') a
order by b
GO

update t set c2 = null
where c2 < 2048 * 2048 / 10
GO


CREATE CLUSTERED INDEX pk ON [t] (c1)
GO

CREATE NONCLUSTERED INDEX i ON t (c2)
GO

Ora, dati questi dati, ho invocato la seguente query:

select * 
from t 
where 
      c2 < 1048576 
   or c2 is null
;

Con mia grande sorpresa, il piano di esecuzione generato per questa query era questo . (Ci scusiamo per il link esterno, è troppo grande per adattarsi qui).

Qualcuno può spiegarmi cosa succede con tutte queste " scansioni costanti " e " scalari di calcolo "? Cosa sta succedendo?

Piano

  |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1010], [Expr1011], [Expr1012]))
       |--Merge Interval
       |    |--Sort(TOP 2, ORDER BY:([Expr1013] DESC, [Expr1014] ASC, [Expr1010] ASC, [Expr1015] DESC))
       |         |--Compute Scalar(DEFINE:([Expr1013]=((4)&[Expr1012]) = (4) AND NULL = [Expr1010], [Expr1014]=(4)&[Expr1012], [Expr1015]=(16)&[Expr1012]))
       |              |--Concatenation
       |                   |--Compute Scalar(DEFINE:([Expr1005]=NULL, [Expr1006]=NULL, [Expr1004]=(60)))
       |                   |    |--Constant Scan
       |                   |--Compute Scalar(DEFINE:([Expr1008]=NULL, [Expr1009]=(1048576), [Expr1007]=(10)))
       |                        |--Constant Scan
       |--Index Seek(OBJECT:([t].[i]), SEEK:([t].[c2] > [Expr1010] AND [t].[c2] < [Expr1011]) ORDERED FORWARD)

Risposte:


29

Le scansioni costanti producono ciascuna una singola riga in memoria senza colonne. Lo scalare di calcolo superiore genera una singola riga con 3 colonne

Expr1005    Expr1006    Expr1004
----------- ----------- -----------
NULL        NULL        60

Lo scalare di calcolo inferiore genera una singola riga con 3 colonne

Expr1008    Expr1009    Expr1007
----------- ----------- -----------
NULL        1048576        10

L'operatore di concatenazione unisce queste 2 righe e genera le 3 colonne ma ora vengono rinominate

Expr1010    Expr1011    Expr1012
----------- ----------- -----------
NULL        NULL        60
NULL        1048576     10

La Expr1012colonna è un insieme di flag utilizzati internamente per definire determinate proprietà di ricerca per il motore di archiviazione .

Il successivo scalare di calcolo lungo le uscite 2 righe

Expr1010    Expr1011    Expr1012    Expr1013    Expr1014    Expr1015
----------- ----------- ----------- ----------- ----------- -----------
NULL        NULL        60          True        4           16            
NULL        1048576     10          False       0           0      

Le ultime tre colonne sono definite come segue e vengono utilizzate solo per scopi di ordinamento prima di presentare all'operatore Intervallo di unione

[Expr1013] = Scalar Operator(((4)&[Expr1012]) = (4) AND NULL = [Expr1010]), 
[Expr1014] = Scalar Operator((4)&[Expr1012]), 
[Expr1015] = Scalar Operator((16)&[Expr1012])

Expr1014e Expr1015basta verificare se alcuni bit sono attivi nella bandiera. Expr1013sembra restituire una colonna booleana vera se entrambi i bit per 4sono attivi e Expr1010attivi NULL.

Provando altri operatori di confronto nella query ottengo questi risultati

+----------+----------+----------+-------------+----+----+---+---+---+---+
| Operator | Expr1010 | Expr1011 | Flags (Dec) |       Flags (Bin)       |
|          |          |          |             | 32 | 16 | 8 | 4 | 2 | 1 |
+----------+----------+----------+-------------+----+----+---+---+---+---+
| >        | 1048576  | NULL     |           6 |  0 |  0 | 0 | 1 | 1 | 0 |
| >=       | 1048576  | NULL     |          22 |  0 |  1 | 0 | 1 | 1 | 0 |
| <=       | NULL     | 1048576  |          42 |  1 |  0 | 1 | 0 | 1 | 0 |
| <        | NULL     | 1048576  |          10 |  0 |  0 | 1 | 0 | 1 | 0 |
| =        | 1048576  | 1048576  |          62 |  1 |  1 | 1 | 1 | 1 | 0 |
| IS NULL  | NULL     | NULL     |          60 |  1 |  1 | 1 | 1 | 0 | 0 |
+----------+----------+----------+-------------+----+----+---+---+---+---+

Da cui deduco che Bit 4 significa "Ha inizio dell'intervallo" (anziché essere illimitato) e Bit 16 indica che l'inizio dell'intervallo è inclusivo.

Questo set di risultati a 6 colonne viene emesso SORTdall'operatore ordinato per Expr1013 DESC, Expr1014 ASC, Expr1010 ASC, Expr1015 DESC. Supponendo Trueè rappresentato da 1e Falseper 0il gruppo di risultati precedentemente rappresentato è già in questo ordine.

Sulla base delle mie ipotesi precedenti, l'effetto netto di questo tipo è quello di presentare gli intervalli all'intervallo di unione nel seguente ordine

 ORDER BY 
          HasStartOfRangeAndItIsNullFirst,
          HasUnboundedStartOfRangeFirst,
          StartOfRange,
          StartOfRangeIsInclusiveFirst

L'operatore dell'intervallo di unione genera 2 righe

Expr1010    Expr1011    Expr1012
----------- ----------- -----------
NULL        NULL        60
NULL        1048576     10

Per ogni riga emessa viene eseguita una ricerca dell'intervallo

Seek Keys[1]: Start:[dbo].[t].c2 > Scalar Operator([Expr1010]), 
               End: [dbo].[t].c2 < Scalar Operator([Expr1011])

Quindi sembrerebbe che vengano eseguite due ricerche. Uno apparentemente > NULL AND < NULLe uno > NULL AND < 1048576. Tuttavia, i flag che vengono passati sembrano modificarlo in IS NULLe < 1048576rispettivamente. Speriamo che @sqlkiwi possa chiarire questo e correggere eventuali inesattezze!

Se si modifica leggermente la query in

select *
from t 
where 
      c2 > 1048576 
   or c2 = 0
;

Quindi il piano appare molto più semplice con una ricerca di indice con predicati di ricerca multipli.

Il piano mostra Seek Keys

Start: c2 >= 0, End: c2 <= 0, 
Start: c2 > 1048576

La spiegazione del perché questo piano più semplice non può essere utilizzato per il caso nel PO è data da SQLKiwi nei commenti al precedente post sul blog collegato .

Una ricerca di indice con più predicati non può mescolare diversi tipi di predicato di confronto (ad es. IsE Eqnel caso del PO). Questa è solo una limitazione attuale del prodotto (ed è presumibilmente il motivo per cui il test di uguaglianza nell'ultima query c2 = 0viene implementato usando >=e <=piuttosto che la semplice uguaglianza cerca di ottenere per la query c2 = 0 OR c2 = 1048576.


Non riesco a individuare nulla nell'articolo di Paul che spieghi la differenza tra le bandiere di [Expr1012]. Puoi dedurre cosa significa il 60/10 qui?
Mark Storey-Smith,

@ MarkStorey-Smith - dice che 62è per un confronto di uguaglianza. Immagino 60debba significare che invece di > AND < come mostrato nel piano in realtà ottieni a >= AND <=meno che non sia una IS NULLbandiera esplicita forse (?) O forse il bit 2indica qualcos'altro non correlato ed 60è ancora uguaglianza come quando lo faccio set ansi_nulls offe lo cambio in c2 = nullrimane ancora60
Martin Smith,

2
@MartinSmith 60 è davvero per un confronto con NULL. Le espressioni del limite dell'intervallo utilizzano NULL per rappresentare "illimitato" a entrambe le estremità. La ricerca è sempre esclusiva, ovvero cerca Inizio:> Espr. & Fine: <Espr anziché inclusivo utilizzando> = e <=. Grazie per il commento sul blog, posterò una risposta o un commento più lungo in risposta al mattino (troppo tardi per renderlo giustizia in questo momento).
Paul White dice GoFundMonica

@SQLKiwi - Grazie. Ha senso. Spero di aver capito prima alcuni dei pezzi mancanti.
Martin Smith,

Grazie mille, lo sto ancora assorbendo, ma sembra spiegare bene le cose, la domanda principale che rimane è quella che stai ponendo a @SQLKiwi sul suo blog. Mediterò ancora qualche giorno sulla tua risposta per assicurarmi di non avere domande di follow-up e accetterò la tua risposta. Grazie ancora, è stato di grande aiuto.
Andrew Savinykh,

13

Le scansioni costanti sono un modo per SQL Server di creare un bucket in cui inserirà qualcosa in seguito nel piano di esecuzione. Ho pubblicato una spiegazione più approfondita qui . Per capire a cosa serve la scansione costante, è necessario approfondire il piano. In questo caso, sono gli operatori di calcolo scalare che vengono utilizzati per popolare lo spazio creato dalla scansione costante.

Gli operatori di calcolo scalare vengono caricati con NULL e il valore 1045876, quindi verranno chiaramente utilizzati con il loop join nel tentativo di filtrare i dati.

La parte davvero interessante è che questo piano è Trivial. Significa che ha attraversato un processo di ottimizzazione minimo. Tutte le operazioni stanno portando all'intervallo di unione. Questo è usato per creare un set minimo di operatori di confronto per una ricerca di indice ( dettagli su questo qui ).

L'idea è di eliminare i valori sovrapposti in modo da poter estrarre i dati con passaggi minimi. Sebbene stia ancora utilizzando un'operazione di loop, noterai che il loop viene eseguito esattamente una volta, il che significa che è effettivamente una scansione.

ADDENDUM: quest'ultima frase è disattivata. C'erano due ricerche. Ho letto male il piano. Il resto dei concetti è lo stesso e l'obiettivo, passaggi minimi, è lo stesso.

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.