Uno degli algoritmi disponibili per unire due tabelle in SQL Server è Nested Loops. Il join di loop nidificato utilizza un input di join come tabella di input esterna e uno come tabella di input interna. Il ciclo esterno esegue l'iterazione della tabella di input esterna riga per riga. Il ciclo interno, eseguito per ogni riga esterna, cerca le righe corrispondenti nella tabella di input interna.
Questo è chiamato join ingenuo di loop nidificati.
Se si dispone di un indice sulle condizioni di join nella tabella di input interna, non è necessario eseguire un ciclo interno per ogni riga della tabella esterna. Puoi invece passare il valore dalla tabella esterna come argomento di ricerca e collegare tutte le righe restituite della tabella interna alle righe della tabella esterna.
La ricerca dalla tabella interna è un accesso casuale. SQL Server, a partire dalla versione 2005, dispone dell'ottimizzazione dell'ordinamento batch (non confondere con l'operatore di ordinamento in modalità batch per gli indici Columnstore). Lo scopo dell'ottimizzazione è ordinare le chiavi di ricerca dalla tabella esterna prima di ottenere i dati da quella interna. Pertanto, un accesso casuale sarà sequenziale.
Il piano di esecuzione non visualizza l'operazione di ordinamento batch come operatore separato. Invece, puoi vedere la proprietà Optimized=true nell'operatore Nested Loops. Se fosse possibile vedere l'ordinamento batch come operatore separato nel piano, sarebbe simile al seguente:
In questo pseudo-piano, leggiamo i dati dall'indice ix_CustomerID non cluster nell'ordine di questa chiave di indice CustomerID. Quindi, è necessario eseguire la ricerca chiave nell'indice cluster, poiché ix_CustomerID non è un indice di copertura. Key Lookup è un'operazione di ricerca della chiave dell'indice cluster, un accesso casuale. Per renderlo sequenziale, SQL Server può eseguire l'ordinamento batch in base alla chiave dell'indice cluster.
Per ulteriori informazioni sull'ordinamento in batch, fare riferimento al mio articolo Ordinamento in batch e cicli nidificati.
Questa ottimizzazione fornisce una grande spinta con un numero sufficiente di righe. Puoi leggere ulteriori informazioni sui risultati dei test nel blog OPTIMIZED Nested Loops Joins, creato da Craig Freedman, uno sviluppatore di ottimizzatori.
Tuttavia, se il numero effettivo di righe è inferiore a quello previsto, i costi aggiuntivi della CPU per creare questo tipo possono nascondere i suoi vantaggi, aumentare il consumo della CPU e ridurne le prestazioni.
Considera questo esempio particolare:
use tempdb; go -- create a test table (SalesOrderID - clustered PK) create table dbo.SalesOrder(SalesOrderID int identity primary key, CustomerID int not null, SomeData char(200) not null); go -- add test data with n as (select top(1000000) rn = row_number() over(order by (select null)) from sys.all_columns c1,sys.all_columns c2) insert dbo.SalesOrder(CustomerID, SomeData) select rn%500000, str(rn,100) from n; -- create a clustered index create index ix_c on dbo.Salesorder(CustomerID); go -- the batch sort optimization is enabled by default (Nested Loops: Optimized = true) select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000; -- disable it with the DISABLE_OPTIMIZED_NESTED_LOOP hint (Nested Loops: Optimized = false) select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000 option(use hint('DISABLE_OPTIMIZED_NESTED_LOOP')); go
Il risultato restituito:
Vorrei attirare la vostra attenzione sul diverso ordine delle righe nell'output. Il server restituisce le righe nell'ordine in cui le elabora poiché non abbiamo specificato esplicitamente ORDER BY. Nel primo caso, leggiamo gradualmente dall'indice ix_c. Tuttavia, per ottimizzare le letture casuali dall'indice cluster, filtriamo le righe in base alla chiave dell'indice SalesOrderID cluster. Nel secondo caso, non c'è alcun ordinamento e le letture vengono eseguite nell'ordine della chiave CustomerID dell'indice non cluster ix_c.
Differenza dal flag di traccia 2340
Nonostante il fatto che la documentazione specifichi il flag di traccia 2340 come equivalente dell'hint DISABLE_OPTIMIZED_NESTED_LOOP, in realtà non è vero.
Considera il seguente esempio in cui userò il comando UPDATE STATISTICS … WITH PAGECOUNT non documentato per ingannare l'ottimizzatore dicendo che la tabella occupa più pagine di quante ne abbia effettivamente. Quindi, dai un'occhiata a queste query:
- Senza alcun suggerimento (ho aggiunto MAXDOP per mantenere un piano semplice);
- Con il suggerimento DISABLE_OPTIMIZED_NESTED_LOOP;
- Con il flag di traccia 2340.
-- create a huge table update statistics dbo.SalesOrder with pagecount = 100000; go set showplan_xml on; go -- 1. without hints select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(maxdop 1); -- 2. hint select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(use hint('DISABLE_OPTIMIZED_NESTED_LOOP'), maxdop 1); -- 3. trace flag select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(querytraceon 2340, maxdop 1); go set showplan_xml off; go
Di conseguenza, abbiamo i seguenti piani:
Nested Loops ha la proprietà Optimized =false in tutti e tre i piani. Il fatto è che aumentando la larghezza della tabella, aumentiamo anche il costo di accesso ai dati. Quando il costo è sufficientemente elevato, SQL Server può utilizzare l'operatore di ordinamento esplicito, anziché l'operatore di ordinamento batch implicito. Possiamo vederlo nel primo piano di query.
Nella seconda query, abbiamo utilizzato l'hint DISABLE_OPTIMIZED_NESTED_LOOP che disattiva l'ordinamento batch implicito. Tuttavia, rimuove un ordinamento esplicito da un operatore separato.
Nel terzo piano, possiamo vedere che, nonostante l'aggiunta del flag di traccia 2340, esiste l'operatore di ordinamento.
Pertanto, la differenza tra l'hint e il flag è la seguente:un hint disabilita l'ottimizzazione trasformando un accesso casuale in uno seriale a seconda che il server lo implementi con l'ordinamento batch implicito o con l'operatore di ordinamento separato.
PS I piani possono dipendere dall'attrezzatura. Pertanto, se non riesci a eseguire queste query, prova ad aumentare o diminuire la dimensione della colonna SomeData char(200) nella descrizione della tabella dbo.SalesOrder.
L'articolo è stato tradotto dal team di Codingsight con il permesso dell'autore.