10

Sto costruendo una vista in SQL Server 2000 (e 2005) e ho notato che l'ordine delle istruzioni di join influenza notevolmente il piano di esecuzione e la velocità della query.Perché le clausole order of join influiscono sul piano di query in SQL Server?

select  sr.WTSASessionRangeID, 
      -- bunch of other columns 
from  WTSAVW_UserSessionRange us 
inner join WTSA_SessionRange sr on sr.WTSASessionRangeID = us.WTSASessionRangeID 
left outer join WTSA_SessionRangeTutor srt on srt.WTSASessionRangeID = sr.WTSASessionRangeID 
left outer join WTSA_SessionRangeClass src on src.WTSASessionRangeID = sr.WTSASessionRangeID 
left outer join WTSA_SessionRangeStream srs on srs.WTSASessionRangeID = sr.WTSASessionRangeID 
--left outer join MO_Stream ms on ms.MOStreamID = srs.MOStreamID 
left outer join WTSA_SessionRangeEnrolmentPeriod srep on srep.WTSASessionRangeID = sr.WTSASessionRangeID 
left outer join WTSA_SessionRangeStudent stsd on stsd.WTSASessionRangeID = sr.WTSASessionRangeID 
left outer join WTSA_SessionSubrange ssr on ssr.WTSASessionRangeID = sr.WTSASessionRangeID 
left outer join WTSA_SessionSubrangeRoom ssrr on ssrr.WTSASessionSubrangeID = ssr.WTSASessionSubrangeID 
left outer join MO_Stream ms on ms.MOStreamID = srs.MOStreamID 

in SQL Server 2000, la query sopra costantemente genera un piano di costo 946. Se io Rimuovere il commento dalla MO_Stream unirsi nel mezzo della query e commentare out quella in basso, il costo scende a 263. La velocità di esecuzione diminuisce di conseguenza. Ho sempre pensato che Query Optimizer avrebbe interpretato la query in modo appropriato senza considerare l'ordine di join, ma sembra che l'ordine sia importante.

Quindi, poiché l'ordine fa sembra avere importanza, c'è una strategia di join che dovrei seguire per scrivere query più veloci?

(Per inciso, in SQL Server 2005, con i dati quasi identici, i costi del piano di query erano 0,675 e 0,631, rispettivamente).

Edit: in SQL Server 2000, ecco le statistiche profilati:

  • 946-cost query: 9094ms CPU, 5121 reads, 0 writes, 10123ms duration
  • 263-cost query: 172ms CPU, 7477 reads, 0 writes, 170ms duration

Modifica: Ecco la struttura logica delle tabelle.

SessionRange ---+--- SessionRangeTutor 
       |--- SessionRangeClass 
       |--- SessionRangeStream --- MO_Stream 
       |--- SessionRangeEnrolmentPeriod 
       |--- SessionRangeStudent 
       +----SessionSubrange --- SessionSubrangeRoom 

Edit: Grazie a Alex e gbn per avermi nella giusta direzione. Ho anche trovato this question.

Ecco la nuova query:

select sr.WTSASessionRangeID // + lots of columns 

from WTSAVW_UserSessionRange us 
inner join WTSA_SessionRange sr on sr.WTSASessionRangeID = us.WTSASessionRangeID 
left outer join WTSA_SessionRangeTutor srt on srt.WTSASessionRangeID = sr.WTSASessionRangeID 
left outer join WTSA_SessionRangeClass src on src.WTSASessionRangeID = sr.WTSASessionRangeID 
left outer join WTSA_SessionRangeEnrolmentPeriod srep on srep.WTSASessionRangeID = sr.WTSASessionRangeID 
left outer join WTSA_SessionRangeStudent stsd on stsd.WTSASessionRangeID = sr.WTSASessionRangeID 

// SessionRangeStream is a many-to-many mapping table between SessionRange and MO_Stream 
left outer join (
    WTSA_SessionRangeStream srs 
    inner join MO_Stream ms on ms.MOStreamID = srs.MOStreamID 
) on srs.WTSASessionRangeID = sr.WTSASessionRangeID 

// SessionRanges MAY have Subranges and Subranges MAY have Rooms 
left outer join (
    WTSA_SessionSubrange ssr  
    left outer join WTSA_SessionSubrangeRoom ssrr on ssrr.WTSASessionSubrangeID = ssr.WTSASessionSubrangeID 
) on ssr.WTSASessionRangeID = sr.WTSASessionRangeID 

costo SQLServer2000: 24,9

risposta

6

Devo non essere d'accordo con tutte le risposte precedenti e il motivo è semplice: se si modifica l'ordine del join sinistro, le query sono logicamente diverse e in quanto tali producono set di risultati diversi. Vedi di persona:

SELECT 1 AS a INTO #t1 
UNION ALL SELECT 2 
UNION ALL SELECT 3 
UNION ALL SELECT 4; 

SELECT 1 AS b INTO #t2 
UNION ALL SELECT 2; 

SELECT 1 AS c INTO #t3 
UNION ALL SELECT 3; 

SELECT a, b, c 
FROM #t1 LEFT JOIN #t2 ON #t1.a=#t2.b 
    LEFT JOIN #t3 ON #t2.b=#t3.c 
ORDER BY a; 

SELECT a, b, c 
FROM #t1 LEFT JOIN #t3 ON #t1.a=#t3.c 
    LEFT JOIN #t2 ON #t3.c=#t2.b 
ORDER BY a; 

a   b   c 
----------- ----------- ----------- 
1   1   1 
2   2   NULL 
3   NULL  NULL 
4   NULL  NULL 

(4 row(s) affected) 

a   b   c 
----------- ----------- ----------- 
1   1   1 
2   NULL  NULL 
3   NULL  3 
4   NULL  NULL 
+0

Dipende dalla struttura delle tabelle. Hai ragione per il caso T1-T2, T2-T3. Nella mia situazione sono T1-T2, T1-T3. – geofftnz

+0

@geofftnz: vedere la mia risposta. Tu sei * non * T1-T2, T1-T3 – gbn

2

Ovviamente, l'ottimizzatore di SQL Server 2005 è molto meglio di quella del 2000 di SQL Server.

Tuttavia, c'è molta verità nella tua domanda. I join esterni causeranno un'esecuzione che varia in modo selvaggio in base all'ordine (i join interni tendono ad essere ottimizzati per il percorso più efficiente, ma, di nuovo, l'ordine conta). Se ci pensi, mentre costruisci i join a sinistra, devi capire cosa diavolo è a sinistra. In quanto tale, ogni join deve essere calcolato prima di poter eseguire ogni altro join. Diventa sequenziale e non parallelo. Ora, ovviamente, ci sono cose che puoi fare per combatterlo (come indici, viste, ecc.). Ma il punto è: il tavolo deve sapere cosa c'è sulla sinistra prima che possa fare un join esterno sinistro. E se continui ad aggiungere join, ottieni sempre più astrazioni su cosa, esattamente a sinistra (specialmente se usi tabelle unite come tabella di sinistra!).

Con i join interni, tuttavia, è possibile parallelizzarli un po ', quindi non c'è una differenza drammatica per quanto riguarda l'ordine.

+0

Grazie per l'input Eric - Ho intenzione di fare ancora un po 'di riorganizzazione per vedere se riesco a ridurre ulteriormente i costi. Ci sono indici su tutte le colonne utilizzate per l'unione.Sfortunatamente la natura dei dati significa che devo usare join esterni per questo. – geofftnz

+0

"Ovviamente, l'ottimizzatore di SQL Server 2005 è molto meglio di quello di SQL Server 2000." Questo è un eufemismo. Non solo è lo scarificatore di query SQL Server 2000 debole (insieme al resto dell'edizione 2000, a mio parere), ma spesso sembra ottimizzare eccessivamente, rendendo false ipotesi che si traducono in dati esilaranti errati. Un'unione di viste o una vista contenente un'unione o una vista contenente una vista (!) Possono scatenare questa idiozia una volta superata una certa soglia di complessità. Non sono mai stato in grado di individuare esattamente la natura del problema, ma sono stato spesso morso da esso. – WCWedin

1

dipende da quale dei campi di join sono indicizzati - se deve scansionare il primo campo della tabella, ma utilizzare un indice sul secondo, è lento. Se il tuo primo campo di join è un indice, sarà più veloce.La mia ipotesi è che il 2005 lo ottimizzi meglio determinando i campi indicizzati ed eseguendo i primi

2

Una strategia generale per l'ottimizzazione delle query contenenti JOINs è quella di esaminare il modello di dati e i dati e cercare di determinare quali JOIN ridurranno il numero di record quello deve essere considerato il più rapidamente. Meno record devono essere considerati, più veloce sarà la query. Generalmente il server produce anche un piano di query migliore.

Insieme con l'ottimizzazione sopra assicurarsi che tutti i campi utilizzati nei join sono indicizzati

1

A DevConnections a pochi anni fa una sessione sulle prestazioni di SQL Server ha dichiarato che (a) ordine di outer join non importa, e (b) quando una query ha molti join, non li guarderà tutti prima di prendere una decisione su un piano. Se sai di avere dei join che aiuteranno a velocizzare una query, dovrebbero essere nella fase iniziale dell'elenco FROM (se puoi).

2

La query è probabilmente sbagliata comunque. Alex ha ragione. Anche Eric potrebbe essere corretto, ma la query è sbagliata.

Lets' prendere questo sottogruppo:

WTSA_SessionRange sr 
left outer join 
WTSA_SessionSubrange ssr on ssr.WTSASessionRangeID = sr.WTSASessionRangeID 
left outer join 
WTSA_SessionSubrangeRoom ssrr on ssrr.WTSASessionSubrangeID = ssr.WTSASessionSubrangeID 

Si stanno unendo WTSA_SessionSubrangeRoom su WTSA_SessionSubrange. Potresti non avere righe da WTSA_SessionSubrange.

join dovrebbe essere questo:

WTSA_SessionRange sr 
left outer join 
(SELECT WTSASessionRangeID, columns I need 
FROM 
    WTSA_SessionSubrange ssr 
    left outer join 
    WTSA_SessionSubrangeRoom ssrr on ssrr.WTSASessionSubrangeID = ssr.WTSASessionSubrangeID 
) foo on foo.WTSASessionRangeID = sr.WTSASessionRangeID 

Questo è il motivo per cui l'ordine di join sta interessando i risultati perché è un diverso interrogazione, dichiarativo parlando.

Devi anche cambiare il join MO_Stream e WTSA_SessionRangeStream.

+0

Quali situazioni risulterebbero in queste due query che restituiscono risultati diversi? – geofftnz

+0

Esattamente come Alex ha dimostrato ... – gbn

+0

Ma nella situazione T1-T2 T2-T3, se c'era una relazione di chiave esterna tra T2 e T3 tale che una riga in T3 non può esistere senza fare riferimento a una riga in T2, ha ancora importanza ? – geofftnz

3

L'ordine di join fa la differenza per la query risultante. Questo è documentato nel BOL nella documentazione per FROM:

<joined_table>

È un set di risultati che è il prodotto di due o più tabelle. Per più join, utilizzare le parentesi per modificare l'ordine naturale dei join.

È possibile modificare l'ordine di unione utilizzando le parentesi attorno ai join (BOL lo mostra nella sintassi nella parte superiore dei documenti, ma è facile perdere).

Questo è noto come comportamento chiastico. È inoltre possibile utilizzare il suggerimento di query OPTION (FORCE ORDER) per forzare uno specifico ordine di join, ma ciò può comportare i cosiddetti "piani cespugliosi" che potrebbero non essere il più ottimale per la query in esecuzione,.