Perché le stime delle righe di SQL Server cambiano quando aggiungo un suggerimento di join?


15

Ho una query che unisce alcune tabelle ed esegue abbastanza male - le stime delle righe sono lontane (1000 volte) e viene scelto il join di cicli annidati, con il risultato di più scansioni di tabelle. La forma della query è abbastanza semplice, simile a questa:

SELECT t1.id
FROM t1
INNER JOIN t2 ON t1.id = t2.t1_id
LEFT OUTER JOIN t3 ON t2.id = t3.t2_id
LEFT OUTER JOIN t4 ON t3.t4_id = t4.id 
WHERE t4.id = some_GUID

Giocando con la query, ho notato che quando suggerisco di utilizzare un join Unisci per uno dei join, viene eseguito molte volte più velocemente. Questo posso capire - Unire join è un'opzione migliore per i dati che vengono uniti, ma SQL Server non lo stima semplicemente scegliendo i loop nidificati.

Quello che non capisco del tutto è perché questo suggerimento di join modifica tutte le stime per tutti gli operatori del piano? Dalla lettura di diversi articoli e libri, ho ipotizzato che le stime della cardinalità fossero eseguite prima della costruzione del piano, quindi l'uso di un suggerimento non avrebbe modificato le stime, ma piuttosto esplicitamente dire a SQL Server di utilizzare una particolare implementazione di join fisico.

Quello che vedo, tuttavia, è che il suggerimento Unisci fa sì che tutte le stime diventino praticamente perfette. Perché questo accade e ci sono tecniche comuni per fare in modo che Query Optimizer effettui una stima migliore senza un suggerimento, considerando che le statistiche ovviamente lo consentono?

UPD: i piani di esecuzione anonimizzati sono disponibili qui: https://www.dropbox.com/s/hchfuru35qqj89s/merge_join.sqlplan?dl=0 https://www.dropbox.com/s/38sjtv0t7vjjfdp/no_hints_join.sqlplan?dl = 0

Ho controllato le statistiche utilizzate da entrambe le query usando TF 3604, 9292 e 9204, e quelle sono identiche. Tuttavia gli indici che vengono scansionati / ricercati differiscono tra le query.

Oltre a ciò, ho provato a eseguire la query con OPTION (FORCE ORDER): funziona ancora più velocemente rispetto all'utilizzo del join unione, scegliendo HASH MATCH per ogni join.


3
Hai notato che hai un join esterno ma stai usando la tabella nella clausola where?
James Z,

@JamesZ - sì, lo so, non penso che ci sia un problema.
Alexander Shelemin,

9
@AlexSh Bene, c'è un problema logico / semantico in ciò, perché questo cambia il tuo join esterno in un join interno.
Aaron Bertrand

Risposte:


21

Dalla lettura di diversi articoli e libri, ho ipotizzato che le stime della cardinalità vengano eseguite prima della costruzione del piano.

Non esattamente. Viene derivata una stima di cardinalità iniziale (dopo semplificazioni e altri lavori), che influenza l'ordine di join iniziale scelto dall'ottimizzatore.

Tuttavia, le successive esplorazioni (durante l'ottimizzazione basata sui costi) possono e spesso comportano il calcolo di nuove stime di cardinalità. Queste CE successive potrebbero essere più o meno "accurate". Se risulta una sottovalutazione, l'ottimizzatore può scegliere un piano che sembra più economico, ma in realtà funziona per molto più tempo.

In generale, non vi è alcuna garanzia che le stime della cardinalità per sottotitoli semanticamente identici produrranno gli stessi risultati. Dopo tutto, si tratta di un processo statistico e alcune operazioni hanno un supporto CE più profondo di altre.

Nel tuo caso, sembra esserci un altro fattore: l'ottimizzatore introduce (o si sposta) un Top, che imposta un obiettivo di riga sulla sottostruttura sottostante:

Frammento di piano

Se dovessi abilitare il flag di traccia 4138 (su 2008 R2 o versione successiva), potresti trovare le stime più in linea con le aspettative, o forse anche che l'ottimizzatore non sceglierebbe più cicli annidati.

Quello che vedo, tuttavia, è che il suggerimento Unisci fa sì che tutte le stime diventino praticamente perfette.

C'è un elemento di fortuna coinvolto qui. Le persone tendono a scrivere query, o almeno i join, nell'ordine in cui si aspettano che vengano eseguite fisicamente. L'uso di un suggerimento di join comporta un implicito FORCE ORDER, che fissa in tal modo l'ordine di join in modo che corrisponda al modulo testuale e disattiva molte regole di esplorazione dell'ottimizzatore che possono portare a una nuova stima della cardinalità.

Oltre a ciò, ho provato a eseguire la query con OPTION (FORCE ORDER): funziona ancora più velocemente rispetto all'utilizzo del join unione, scegliendo HASH MATCH per ogni join.

Ciò equivale a suggerire un join, ma non limita la scelta dell'operatore di join fisico. Ancora una volta, se ti è capitato di scrivere l'ordine di join della query in modo logico, è molto probabile che otterrai un piano ragionevole. Naturalmente, in questo modo perdi gran parte delle capacità dell'ottimizzatore, che potrebbe non produrre risultati ottimali in situazioni più generali.

Probabilmente non vorrai usarlo FORCE ORDERmolto spesso perché è un suggerimento (direttiva) estremamente potente che ha effetti più ampi del semplice forzare l'ordine dei join; ad esempio, impedisce all'ottimizzatore di spostare gli aggregati e di introdurre aggregazioni parziali. Consiglio vivamente di non utilizzare questo suggerimento, tranne in circostanze eccezionali, e di sintonizzatori veramente esperti .

Un'analisi dettagliata richiederebbe più tempo di quello che ho in questo momento e l'accesso a una copia solo statistica del database.


-10

Il dove nega la sinistra
Perché rendere difficile l'ottimizzatore?
A 3 o più join l'ottimizzatore TENDERà a diventare difensivo e ad entrare in loop in modo da proteggere la memoria
Una o condizione nell'unione tenderà anche ad entrare in un loop loop - ho prove concrete che accadrà ogni volta - no - ancora una realtà
Con più join estrarre le condizioni da dove nel join quando è possibile

SELECT t1.id
  FROM t1
  JOIN t2 
        ON t1.id = t2.t1_id
  JOIN t3 
        ON t2.id = t3.t2_id
  JOIN t4 
        ON t3.t4_id = t4.id 
       AND t4.id = some_GUID 

O ancora meglio: scommetto che questo incontrerà o batterà i tuoi suggerimenti o la tua forza

SELECT t1.id
  FROM t1
  JOIN t2 
        ON t1.id = t2.t1_id
  JOIN t3 
        ON t2.id = t3.t2_id
       AND t3.t4_id = some_GUID

Il problema con i suggerimenti è che riguardano i dati in uno stato specifico. Scrivi una query pulita e lascia che l'ottimizzatore faccia il suo lavoro. Alcune volte ha solo bisogno di più statistiche per fare la cosa giusta, ma poi si bloccherà.

Perché stime diverse. Un piano diverso. Inizia con query che danno all'ottimizzatore una possibilità di combattimento.

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.