INNER JOIN vs LEFT JOIN in SQL Server


259

Ho creato il comando SQL che utilizza INNER JOIN su 9 tabelle, tuttavia questo comando richiede molto tempo (più di cinque minuti). Quindi la mia gente mi ha suggerito di cambiare INNER JOIN in LEFT JOIN perché le prestazioni di LEFT JOIN sono migliori, nonostante quello che so. Dopo averlo modificato, la velocità della query è stata notevolmente migliorata.

Vorrei sapere perché LEFT JOIN è più veloce di INNER JOIN?

Il mio comando SQL è simile al seguente: SELECT * FROM A INNER JOIN B ON ... INNER JOIN C ON ... INNER JOIN De così via

Aggiornamento: questo è un breve riassunto del mio schema.

FROM sidisaleshdrmly a -- NOT HAVE PK AND FK
    INNER JOIN sidisalesdetmly b -- THIS TABLE ALSO HAVE NO PK AND FK
        ON a.CompanyCd = b.CompanyCd 
           AND a.SPRNo = b.SPRNo 
           AND a.SuffixNo = b.SuffixNo 
           AND a.dnno = b.dnno
    INNER JOIN exFSlipDet h -- PK = CompanyCd, FSlipNo, FSlipSuffix, FSlipLine
        ON a.CompanyCd = h.CompanyCd
           AND a.sprno = h.AcctSPRNo
    INNER JOIN exFSlipHdr c -- PK = CompanyCd, FSlipNo, FSlipSuffix
        ON c.CompanyCd = h.CompanyCd
           AND c.FSlipNo = h.FSlipNo 
           AND c.FSlipSuffix = h.FSlipSuffix 
    INNER JOIN coMappingExpParty d -- NO PK AND FK
        ON c.CompanyCd = d.CompanyCd
           AND c.CountryCd = d.CountryCd 
    INNER JOIN coProduct e -- PK = CompanyCd, ProductSalesCd
        ON b.CompanyCd = e.CompanyCd
           AND b.ProductSalesCd = e.ProductSalesCd 
    LEFT JOIN coUOM i -- PK = UOMId
        ON h.UOMId = i.UOMId 
    INNER JOIN coProductOldInformation j -- PK = CompanyCd, BFStatus, SpecCd
        ON a.CompanyCd = j.CompanyCd
            AND b.BFStatus = j.BFStatus
            AND b.ProductSalesCd = j.ProductSalesCd
    INNER JOIN coProductGroup1 g1 -- PK = CompanyCd, ProductCategoryCd, UsedDepartment, ProductGroup1Cd
        ON e.ProductGroup1Cd  = g1.ProductGroup1Cd
    INNER JOIN coProductGroup2 g2 -- PK = CompanyCd, ProductCategoryCd, UsedDepartment, ProductGroup2Cd
        ON e.ProductGroup1Cd  = g2.ProductGroup1Cd

1
Proiettate qualche attributo da coUOM? Altrimenti potresti essere in grado di utilizzare un semi join. Se sì, potresti usarlo UNIONcome alternativa. Pubblicare solo la tua FROMclausola è un'informazione inadeguata qui.
Onedayquen

1
Mi sono chiesto così spesso (perché vedo sempre).
Paul Draper,

1
Ti sei perso un Order By nel tuo breve schema? Di recente ho riscontrato un problema in cui la modifica di UNNER JOIN in LEFT OUTER JOIN accelera la query da 3 minuti a 10 secondi. Se hai davvero Ordina per nella tua query, spiegherò ulteriormente come risposta. Sembrava che tutte le risposte non spiegassero davvero il caso che ho affrontato.
Phuah Yee Keat,

Risposte:


403

A non LEFT JOINè assolutamente più veloce di un INNER JOIN. In effetti, è più lento; per definizione, un join esterno ( LEFT JOINo RIGHT JOIN) deve fare tutto il lavoro di un INNER JOINplus e il lavoro extra di null-extending i risultati. Ci si aspetterebbe inoltre di restituire più righe, aumentando ulteriormente il tempo di esecuzione totale semplicemente a causa delle dimensioni maggiori del set di risultati.

(E anche se a LEFT JOIN fosse più veloce in situazioni specifiche a causa di una confluenza di fattori difficile da immaginare, non è funzionalmente equivalente a un INNER JOIN, quindi non puoi semplicemente andare a sostituire tutte le istanze dell'una con l'altra!)

Molto probabilmente i tuoi problemi di prestazioni risiedono altrove, come non avere una chiave candidata o una chiave esterna indicizzata correttamente. 9 tavoli sono molti da unire, quindi il rallentamento potrebbe letteralmente essere ovunque. Se pubblichi il tuo schema, potremmo essere in grado di fornire maggiori dettagli.


Modificare:

Riflettendo ulteriormente su questo, potrei pensare a una circostanza in cui una LEFT JOINpotrebbe essere più veloce di una INNER JOIN, e cioè quando:

  • Alcune tabelle sono molto piccole (diciamo, sotto 10 righe);
  • Le tabelle non hanno indici sufficienti per coprire la query.

Considera questo esempio:

CREATE TABLE #Test1
(
    ID int NOT NULL PRIMARY KEY,
    Name varchar(50) NOT NULL
)
INSERT #Test1 (ID, Name) VALUES (1, 'One')
INSERT #Test1 (ID, Name) VALUES (2, 'Two')
INSERT #Test1 (ID, Name) VALUES (3, 'Three')
INSERT #Test1 (ID, Name) VALUES (4, 'Four')
INSERT #Test1 (ID, Name) VALUES (5, 'Five')

CREATE TABLE #Test2
(
    ID int NOT NULL PRIMARY KEY,
    Name varchar(50) NOT NULL
)
INSERT #Test2 (ID, Name) VALUES (1, 'One')
INSERT #Test2 (ID, Name) VALUES (2, 'Two')
INSERT #Test2 (ID, Name) VALUES (3, 'Three')
INSERT #Test2 (ID, Name) VALUES (4, 'Four')
INSERT #Test2 (ID, Name) VALUES (5, 'Five')

SELECT *
FROM #Test1 t1
INNER JOIN #Test2 t2
ON t2.Name = t1.Name

SELECT *
FROM #Test1 t1
LEFT JOIN #Test2 t2
ON t2.Name = t1.Name

DROP TABLE #Test1
DROP TABLE #Test2

Se esegui questo e visualizzi il piano di esecuzione, vedrai che la INNER JOINquery ha effettivamente un costo maggiore rispetto a quella LEFT JOIN, perché soddisfa i due criteri sopra. È perché SQL Server vuole eseguire una corrispondenza hash per il INNER JOINma esegue cicli nidificati per il LEFT JOIN; il primo è normalmente molto più veloce, ma poiché il numero di righe è così piccolo e non c'è indice da usare, l'operazione di hashing risulta essere la parte più costosa della query.

Puoi vedere lo stesso effetto scrivendo un programma nel tuo linguaggio di programmazione preferito per eseguire un gran numero di ricerche in un elenco con 5 elementi, rispetto a una tabella hash con 5 elementi. A causa delle dimensioni, la versione della tabella hash è in realtà più lenta. Ma aumentalo a 50 elementi, o 5000 elementi, e la versione dell'elenco rallenta a una scansione, perché è O (N) vs. O (1) per l'hashtable.

Ma cambia questa query in modo che sia nella IDcolonna anziché Namee vedrai una storia molto diversa. In tal caso, esegue cicli annidati per entrambe le query, ma la INNER JOINversione è in grado di sostituire una delle scansioni dell'indice cluster con una ricerca, il che significa che questo sarà letteralmente un ordine di grandezza più veloce con un gran numero di righe.

Quindi la conclusione è più o meno quella che ho citato diversi paragrafi sopra; questo è quasi certamente un problema di indicizzazione o copertura dell'indice, eventualmente combinato con una o più tabelle molto piccole. Queste sono le uniche circostanze in cui a volte SQL Server potrebbe scegliere un piano di esecuzione peggiore per un INNER JOINa LEFT JOIN.


4
Esiste un altro scenario che può portare a un OUTER JOIN che funziona meglio di un INNER JOIN. Vedi la mia risposta qui sotto.
dbenham,

12
Voglio sottolineare che non esiste praticamente alcuna documentazione di database a supporto dell'idea che le prestazioni dei join interni e esterni si differenzino. I join esterni sono leggermente più costosi dei join interni, a causa del volume dei dati e delle dimensioni del set di risultati. Tuttavia, gli algoritmi sottostanti ( msdn.microsoft.com/en-us/library/ms191426(v=sql.105).aspx ) sono gli stessi per entrambi i tipi di join. Le prestazioni dovrebbero essere simili quando restituiscono quantità simili di dati.
Gordon Linoff,

3
@Aaronaught. . . Questa risposta è stata citata in un commento che diceva qualcosa secondo cui "i join esterni hanno prestazioni significativamente peggiori dei join interni". Ho commentato solo per essere sicuro che questa interpretazione errata non si diffonde.
Gordon Linoff,

16
Penso che questa risposta sia fuorviante in un aspetto importante: perché afferma "UN JOIN SINISTRA non è assolutamente più veloce di un JOIN INNER". Questa riga non è corretta. In teoria non è più veloce di un INNER JOIN. NON è "assolutamente non più veloce". La domanda è specificamente una domanda di prestazione. In pratica ora ho visto alcuni sistemi (di aziende molto grandi!) In cui INNER JOIN era incredibilmente lento rispetto a OUTER JOIN. Teoria e pratica sono cose molto diverse.
David Frenkel,

5
@DavidFrenkel: è altamente improbabile. Chiederei di vedere un confronto A / B, con piani di esecuzione, se ritieni che una tale discrepanza sia possibile. Probabilmente è correlato a piani di esecuzione / query memorizzati nella cache o statistiche errate.
Aaronaught,

127

Esiste uno scenario importante che può portare a un join esterno più veloce di un join interno che non è stato ancora discusso.

Quando si utilizza un join esterno, l'ottimizzatore è sempre libero di eliminare la tabella unita esterna dal piano di esecuzione se le colonne di join sono PK della tabella esterna e nessuna delle colonne della tabella esterna fa riferimento all'esterno del join esterno stesso. Ad esempio, SELECT A.* FROM A LEFT OUTER JOIN B ON A.KEY=B.KEYB.KEY è il PK per B. Sia Oracle (credo che stavo usando la versione 10) sia Sql Server (ho usato 2008 R2) potare la tabella B dal piano di esecuzione.

Lo stesso non è necessariamente vero per un join interno: SELECT A.* FROM A INNER JOIN B ON A.KEY=B.KEYpuò o non può richiedere B nel piano di esecuzione a seconda di quali vincoli esistono.

Se A.KEY è una chiave esterna annullabile che fa riferimento a B.KEY, l'ottimizzatore non può eliminare B dal piano perché deve confermare l'esistenza di una riga B per ogni riga A.

Se A.KEY è una chiave esterna obbligatoria che fa riferimento a B.KEY, l'ottimizzatore è libero di eliminare B dal piano perché i vincoli garantiscono l'esistenza della riga. Ma solo perché l'ottimizzatore può eliminare la tabella dal piano, non significa che lo farà. SQL Server 2008 R2 NON elimina B dal piano. Oracle 10 rilascia B dal piano. In questo caso è facile vedere come il join esterno supererà il join interno su SQL Server.

Questo è un esempio banale e non pratico per una query autonoma. Perché iscriversi a un tavolo se non è necessario?

Ma questa potrebbe essere una considerazione progettuale molto importante quando si progettano viste. Spesso viene creata una vista "fai tutto" che unisce tutto ciò di cui un utente potrebbe aver bisogno in relazione a una tabella centrale. (Soprattutto se ci sono utenti ingenui che eseguono query ad hoc che non comprendono il modello relazionale) La vista può includere tutte le colonne pertinenti di molte tabelle. Ma gli utenti finali possono accedere alle colonne solo da un sottoinsieme delle tabelle all'interno della vista. Se le tabelle sono unite con join esterni, l'ottimizzatore può (e fa) eliminare le tabelle non necessarie dal piano.

È fondamentale assicurarsi che la vista utilizzando i join esterni dia i risultati corretti. Come ha detto Aaronaught, non puoi sostituire ciecamente OUTER JOIN con INNER JOIN e aspettarti gli stessi risultati. Ma ci sono momenti in cui può essere utile per motivi di prestazioni quando si usano le viste.

Un'ultima nota: non ho testato l'impatto sulle prestazioni alla luce di quanto sopra, ma in teoria sembra che dovresti essere in grado di sostituire in modo sicuro un INNER JOIN con un OUTER JOIN se aggiungi anche la condizione <FOREIGN_KEY> NON È NULL alla clausola where.


5
In realtà ho riscontrato questo problema durante la creazione di query estremamente dinamiche. Avevo lasciato un INNER JOIN che stavo usando e non estraevo dati da, e quando li ho passati a un JOIN SINISTRA (per curiosità di taglio) la query si è effettivamente svolta più velocemente.
Erik Philips,

1
EDIT: chiarite le condizioni che devono esistere affinché l'ottimizzatore elimini la tabella unita esterna dal piano di esecuzione.
dbenham,

2
Un piccolo chiarimento alla tua risposta: quando la colonna della chiave esterna non è nulla, INNER JOIN e LEFT JOIN diventano semanticamente equivalenti (ovvero la clausola WHERE suggerita è ridondante); l'unica differenza sarebbe il piano di esecuzione.
Douglas,

2
Anche se questo mostra un esempio apparentemente banale, questa è una risposta straordinariamente penetrante!
pbalaga,

6
+1: sembra che mi sia imbattuto in questo in alcune query in cui stavo usando i join interni con tabelle molto grandi. Il join interno stava causando una fuoriuscita in tempdb nel piano di query (presumo per il motivo sopra indicato - e il mio server mancava della RAM per contenere tutto in memoria). Il passaggio ai join di sinistra ha eliminato la fuoriuscita in tempdb, risultando che alcune delle mie query di 20-30 secondi ora vengono eseguite in frazioni di secondo. Questo è un aspetto molto importante visto che la maggior parte delle persone sembra supporre che i join interni siano più veloci.
phosplait,

23

Se tutto funziona come non dovrebbe, MA sappiamo tutti che non funziona come dovrebbe, specialmente quando si tratta di Query Optimizer, cache del piano di query e statistiche.

Per prima cosa suggerirei di ricostruire l'indice e le statistiche, quindi svuotare la cache del piano di query solo per assicurarmi che non stia rovinando le cose. Tuttavia ho riscontrato problemi anche quando è stato fatto.

Ho riscontrato alcuni casi in cui un join sinistro è stato più veloce di un join interno.

Il motivo di fondo è questo: se hai due tabelle e ti unisci su una colonna con un indice (su entrambe le tabelle). Il join interno produrrà lo stesso risultato, indipendentemente dal fatto che si eseguano il loop delle voci nell'indice della tabella uno e si corrisponda con l'indice della tabella due come se si farebbe il contrario: eseguire il loop delle voci nell'indice della tabella due e corrispondere con l'indice nella tabella uno. Il problema è quando si hanno statistiche fuorvianti, Query Optimizer utilizzerà le statistiche dell'indice per trovare la tabella con le voci meno corrispondenti (in base agli altri criteri). Se hai due tabelle con 1 milione ciascuna, nella tabella uno hai 10 righe corrispondenti e nella tabella due hai 100000 righe corrispondenti. Il modo migliore sarebbe fare una scansione dell'indice sulla tabella uno e abbinarla 10 volte nella tabella due. Il contrario sarebbe una scansione dell'indice che esegue il loop su oltre 100000 righe e tenta di corrispondere 100000 volte e solo 10 riescono. Pertanto, se le statistiche non sono corrette, l'ottimizzatore potrebbe scegliere la tabella e l'indice errati da ripetere.

Se l'ottimizzatore sceglie di ottimizzare il join sinistro nell'ordine in cui è scritto, funzionerà meglio del join interno.

MA, l'ottimizzatore può anche ottimizzare un join sinistro in modo subottimale come un semi join sinistro. Per farlo, scegli quello che desideri, puoi usare il suggerimento per forzare l'ordine.


18

Prova entrambe le query (quella con join interno e sinistro) con OPTION (FORCE ORDER)alla fine e pubblica i risultati. OPTION (FORCE ORDER)è un suggerimento per la query che impone all'ottimizzatore di creare il piano di esecuzione con l'ordine di join fornito nella query.

Se INNER JOINinizia a esibirsi più velocemente LEFT JOIN, è perché:

  • In una query composta interamente da INNER JOINs, l'ordine di join non ha importanza. Questo dà la libertà all'ottimizzatore di query di ordinare i join come ritiene opportuno, quindi il problema potrebbe dipendere dall'ottimizzatore.
  • Con LEFT JOIN, non è così perché la modifica dell'ordine di join altererà i risultati della query. Ciò significa che il motore deve seguire l'ordine di join fornito sulla query, che potrebbe essere migliore di quello ottimizzato.

Non so se questo risponde alla tua domanda, ma una volta ero in un progetto che prevedeva query molto complesse che effettuavano calcoli, che ha completamente incasinato l'ottimizzatore. Abbiamo avuto casi in cui un FORCE ORDERavrebbe ridotto il tempo di esecuzione di una query da 5 minuti a 10 secondi.


9

Hanno effettuato una serie di confronti tra i giunti esterni e quelli interni a sinistra e non sono stati in grado di trovare una differenza consistente. Ci sono molte variabili Sto lavorando su un database di report con migliaia di tabelle molte con un gran numero di campi, molte modifiche nel tempo (versioni del fornitore e flusso di lavoro locale). Non è possibile creare tutte le combinazioni di indici di copertura per soddisfare le esigenze di una così ampia varietà di query e gestire i dati storici. Le query interne hanno interrotto le prestazioni del server perché due tabelle di grandi dimensioni (da milioni a decine di milioni di righe) sono interne unite, entrambe eseguono un numero elevato di campi e non esiste alcun indice di copertura.

Il problema più grande, tuttavia, non sembra risolto nelle discussioni di cui sopra. Forse il tuo database è ben progettato con trigger ed elaborazione delle transazioni ben progettata per garantire buoni dati. Il mio ha spesso valori NULL dove non sono previsti. Sì, le definizioni della tabella potrebbero imporre no-Nulls ma questa non è un'opzione nel mio ambiente.

Quindi la domanda è ... progettate la vostra query solo per la velocità, una priorità più alta per l'elaborazione delle transazioni che esegue lo stesso codice migliaia di volte al minuto. Oppure vai per la precisione che fornirà un join esterno sinistro. Ricorda che i join interni devono trovare corrispondenze su entrambi i lati, quindi un NULL imprevisto rimuoverà non solo i dati dalle due tabelle, ma probabilmente intere righe di informazioni. E succede così bene, nessun messaggio di errore.

Puoi essere molto veloce poiché ottenere il 90% dei dati necessari e non scoprire i join interni hanno rimosso silenziosamente le informazioni. A volte i join interni possono essere più veloci, ma non credo che nessuno abbia fatto questa ipotesi a meno che non abbiano rivisto il piano di esecuzione. La velocità è importante, ma la precisione è più importante.


8

È più probabile che i tuoi problemi di prestazioni siano dovuti al numero di join che stai facendo e al fatto che le colonne su cui stai unendo abbiano o meno degli indici.

Nel peggiore dei casi potresti facilmente eseguire 9 scansioni dell'intera tabella per ogni join.


7

I join esterni possono offrire prestazioni superiori se utilizzati nelle viste.

Supponi di avere una query che coinvolge una vista e che la vista sia composta da 10 tabelle unite insieme. Supponiamo che la tua query utilizzi solo colonne di 3 di queste 10 tabelle.

Se quelle 10 tabelle fossero state unite tra loro insieme, allora Query Optimizer dovrebbe unirle tutte anche se la query stessa non ha bisogno di 7 su 10 delle tabelle. Questo perché gli stessi join interni potrebbero filtrare i dati, rendendoli essenziali per il calcolo.

Se invece quelle 10 tabelle fossero state unite esternamente, il Query Optimizer si unirebbe solo a quelle necessarie: 3 su 10 di queste in questo caso. Questo perché i join stessi non filtrano più i dati e quindi i join non utilizzati possono essere ignorati.

Fonte: http://www.sqlservercentral.com/blogs/sql_coach/2010/07/29/poor-little-misunderstood-views/


1
La tua affermazione su "outer-join" è fuorviante e potenzialmente errata. Esterno significa che i dati sull'altro lato non devono necessariamente esistere - e se non sostituiscono NULL. In circostanze specifiche, l'RDBMS può "saltarli" (vedere la risposta precedente di dbenham). TUTTAVIA - esterno vs interno può far sì che la tua query restituisca risultati radicalmente diversi. INTERNI significa: fornire risultati per i quali un articolo è ENTRAMBI A e B. SINISTRA ESTERNO significa tutto A, e facoltativamente B se esiste. Primo caso: ottieni alcune righe, nel secondo ottieni TUTTE le righe.
Ripvlan,

1
@ripvlan Naturalmente, i join esterni e interni non sono sempre intercambiabili. La domanda originale era sulle prestazioni, il che implica che stiamo parlando di casi in cui entrambi i join restituirebbero lo stesso set di risultati.
MarredCheese,

1
Sì e - l'unità ESTERNA potrebbe causare un problema di prestazioni perché causerà la restituzione di tutte le righe (più dati). Il tuo presupposto che le query producano lo stesso output è giusto - tuttavia non è vero nel caso generale e specifico per ogni progetto db. E per coloro che non hanno familiarità con l'algebra relazionale potrebbero causare loro dolore. Il mio punto è solo quello di offrire maggiori informazioni alle persone che leggono questo in cerca di consigli e che un SINISTRA / DESTRA non risolverà magicamente un problema e potrebbe causare più problemi. È rimasto un potere per il livello 300 :-)
ripvlan

2

Ho trovato qualcosa di interessante in SQL Server quando ho verificato se i join interni sono più veloci dei join di sinistra.

Se non si includono gli elementi della tabella dei join di sinistra, nell'istruzione select, il join di sinistra sarà più veloce della stessa query con il join interno.

Se includi la tabella dei join di sinistra nell'istruzione select, il join interno con la stessa query era uguale o più veloce del join di sinistra.


0

Dai miei confronti, trovo che abbiano esattamente lo stesso piano di esecuzione. Esistono tre scenari:

  1. Se e quando restituiscono gli stessi risultati, hanno la stessa velocità. Tuttavia, dobbiamo tenere presente che non sono le stesse query e che LEFT JOIN probabilmente restituirà più risultati (quando alcune condizioni ON non sono soddisfatte) --- questo è il motivo per cui di solito è più lento.

  2. Quando la tabella principale (prima non const nel piano di esecuzione) presenta una condizione restrittiva (WHERE id =?) E la condizione ON corrispondente si trova su un valore NULL, la tabella "right" non viene unita --- questo è quando LEFT JOIN è più veloce.

  3. Come discusso al punto 1, di solito INNER JOIN è più restrittivo e restituisce meno risultati ed è quindi più veloce.

Entrambi usano (gli stessi) indici.

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.