Query JOIN vs query multiple


182

Le query JOIN sono più veloci di diverse query? (Esegui la query principale e quindi esegui molti altri SELECT in base ai risultati della query principale)

Sto chiedendo perché l'adesione a loro complicherebbe MOLTO il design della mia applicazione

Se sono più veloci, qualcuno può approssimarsi molto approssimativamente di quanto? Se è 1,5x non mi interessa, ma se è 10x credo di si.


Immagino che sarebbero più veloci. So che un INSERT rispetto a dire 10 singole query INSERT è molto più veloce.
alex

1
Potrebbe essere importante se le tue query multiple sono all'interno di una procedura memorizzata se provengono dall'applicazione (modifica la tua domanda con queste informazioni). Il primo sarà molto più veloce del successivo.
colithium,

Risposte:


84

Questo è troppo vago per darti una risposta pertinente al tuo caso specifico. Dipende da molte cose. Jeff Atwood (fondatore di questo sito) in realtà ha scritto su questo . Per la maggior parte, tuttavia, se si dispone degli indici giusti e si eseguono correttamente i JOIN, di solito sarà più veloce fare 1 viaggio rispetto a diversi.


2
se si uniscono 3 o più tabelle su chiavi diverse, spesso i database (ad esempio mysql) possono utilizzare solo un indice per tabella, il che significa che forse uno dei join sarà veloce (e utilizzerà un indice) mentre gli altri saranno estremamente lenti. Per più query, è possibile ottimizzare gli indici da utilizzare per ogni query.
user151975

4
Penso che questo dipenda dalla tua definizione di "più veloce" ... ad esempio, 3 join interni PK possono girare più velocemente di 4 round trip, a causa del sovraccarico della rete e perché è necessario interrompere e preparare e inviare ogni query dopo il la query precedente è stata completata. Se si dovesse eseguire il benchmarking di un server sotto carico, tuttavia, nella maggior parte dei casi, i join richiederanno più tempo CPU rispetto alle query PK e spesso causeranno anche un sovraccarico di rete.
mindplay.dk,

98

Per i join interni, una singola query ha senso, poiché si ottengono solo righe corrispondenti. Per i join di sinistra, le query multiple sono molto meglio ... guarda il seguente benchmark che ho fatto:

  1. Interrogazione singola con 5 join

    query: 8.074508 secondi

    dimensione del risultato: 2268000

  2. 5 query di seguito

    tempo di query combinato: 0,00262 secondi

    dimensione del risultato: 165 (6 + 50 + 7 + 12 + 90)

.

Si noti che otteniamo gli stessi risultati in entrambi i casi (6 x 50 x 7 x 12 x 90 = 2268000)

i join di sinistra utilizzano in modo esponenziale più memoria con dati ridondanti.

Il limite di memoria potrebbe non essere così grave se si esegue solo un join di due tabelle, ma in genere tre o più e diventa diverse query.

Come nota a margine, il mio server MySQL è proprio accanto al mio server delle applicazioni ... quindi il tempo di connessione è trascurabile. Se il tempo di connessione è in secondi, forse c'è un vantaggio

Franco


31
Se mettiamo da parte il fastidioso fatto che nessuno nella loro mente giusta fa un cross join tra 5 tavoli (proprio per questo, insieme a quello nella maggior parte dei casi non ha senso ), il tuo "benchmark" potrebbe avere qualche merito . Ma i join di sinistra o interni sono la norma, di solito per chiave (rendendo il recupero molto più veloce) e la duplicazione dei dati è di solito molto, molto meno di quanto tu stia realizzando.
cHao,

12
@cHao dice chi? Ho appena cercato SMF e phpBB e ho visto JOIN tra 3 tabelle: se aggiungi plugin o modifiche potrebbero facilmente aggiungerlo. Qualsiasi tipo di applicazione di grandi dimensioni ha il potenziale per molti JOIN. Probabilmente un ORM mal scritto / usato male potrebbe unire le tabelle che in realtà non ha bisogno (forse anche ogni tabella).
Natalie Adams,

5
@NathanAdams: i join sinistro e interno non sono affatto male. (In effetti, se non si uniscono le tabelle qua e là, si sta facendo SQL errato.) Quello di cui stavo parlando sono i cross join , che sono quasi sempre indesiderabili anche tra due tabelle, figuriamoci 5 - e che riguardare l'unico modo per ottenere i risultati "2268000" altrimenti totalmente falsi di cui sopra.
cHao,

2
Guarda i risultati, però. "dimensione del risultato: 2268000" rispetto a "dimensione del risultato: 165". Penso che il tuo rallentamento con JOINs sia dovuto al fatto che i tuoi record hanno una relazione uno-a-molti, mentre se avessero avuto una relazione uno-a-uno, i JOIN sarebbero assolutamente molto più veloci e certamente non avrebbero un risultato dimensione maggiore di SELEZIONA.
HoldOffHunger

3
@cHao Ovviamente non hai incontrato Magento al momento del tuo primo commento
vitoriodachef,

27

Questa domanda è vecchia, ma mancano alcuni parametri di riferimento. Ho confrontato JOIN con i suoi 2 concorrenti:

  • N + 1 query
  • 2 query, la seconda utilizza un WHERE IN(...)o equivalente

Il risultato è chiaro: su MySQL JOINè molto più veloce. Le query N + 1 possono ridurre drasticamente le prestazioni di un'applicazione:

ISCRIVITI vs WHERE IN vs N + 1

Cioè, a meno che non selezioni molti record che indicano un numero molto piccolo di record distinti, stranieri. Ecco un punto di riferimento per il caso estremo:

JOIN vs N + 1 - tutti i record che puntano allo stesso record straniero

È molto improbabile che ciò accada in un'applicazione tipica, a meno che non si stia unendo una relazione tra molti, nel qual caso la chiave esterna si trova sull'altra tabella e si duplicano più volte i dati della tabella principale.

Porta via:

  • Per le relazioni *-to-one, utilizzare sempre JOIN
  • Per relazioni * -to-many, una seconda query potrebbe essere più veloce

Vedi il mio articolo su Medium per ulteriori informazioni.


22

In realtà sono arrivato a questa domanda cercando una risposta da solo, e dopo aver letto le risposte fornite posso solo concordare sul fatto che il modo migliore per confrontare le prestazioni delle query DB è ottenere numeri del mondo reale perché ci sono solo molte variabili da prendere in considerazione MA, penso anche che il confronto tra i numeri non porti a nulla in quasi tutti i casi. Ciò che intendo è che i numeri dovrebbero sempre essere confrontati con un numero accettabile e sicuramente non confrontati tra loro.

Posso capire se un modo di interrogare richiede 0,02 secondi e l'altro impiega 20 secondi, questa è una differenza enorme. Ma cosa succede se un modo di interrogare richiede 0,0000000002 secondi e l'altro impiega 0,0000002 secondi? In entrambi i casi un modo è enorme 1000 volte più veloce dell'altro, ma è davvero "enorme" nel secondo caso?

In conclusione, come la vedo io personalmente: se funziona bene, cerca la soluzione semplice.


4
Questo, ovviamente, a seconda che tu abbia intenzione di ridimensionare o meno. Perché quando Facebook ha iniziato sono sicuro che avevano quel tipo di domande, ma avevano in mente il ridimensionamento e optarono per la soluzione più efficiente anche se forse più complessa.
Dudewad,

@dudewad Ha senso. Tutto dipende da ciò di cui hai bisogno, alla fine.
Valentin Flachsel,

4
Haha sì ... perché a google 1 nanosecondo perso è letteralmente uguale a qualcosa come 10 miliardi di miliardi di dollari ... ma questa è solo una voce.
Dudewad,

2
@dudewad In realtà, quando Facebook ha iniziato, garantisco che sono andati con la soluzione più semplice. Zuckerberg ha dichiarato di aver programmato la prima versione in sole 2 settimane. Le start-up devono muoversi rapidamente per competere e quelle che sopravvivono di solito non si preoccupano del ridimensionamento fino a quando non ne hanno davvero bisogno. Quindi rifattorizzano le cose dopo avere milioni di dollari di investimento e possono assumere programmatori rockstar specializzati in prestazioni. Secondo te, mi aspetterei che Facebook spesso scelga la soluzione più complessa per ottenere minime prestazioni, ma poi la maggior parte di noi non sta programmando Facebook.
dallin

15

Ha effettuato un test rapido selezionando una riga da una tabella di 50.000 righe e unendo una riga da una tabella di 100.000 righe. Fondamentalmente sembrava:

$id = mt_rand(1, 50000);
$row = $db->fetchOne("SELECT * FROM table1 WHERE id = " . $id);
$row = $db->fetchOne("SELECT * FROM table2 WHERE other_id = " . $row['other_id']);

vs

$id = mt_rand(1, 50000);
$db->fetchOne("SELECT table1.*, table2.*
    FROM table1
    LEFT JOIN table1.other_id = table2.other_id
    WHERE table1.id = " . $id);

Il metodo a due selezioni ha richiesto 3,7 secondi per 50.000 letture, mentre JOIN ha impiegato 2,0 secondi sul mio computer lento a casa. INNER JOIN e LEFT JOIN non hanno fatto differenza. Il recupero di più righe (ad esempio, utilizzando IN SET) ha prodotto risultati simili.


1
Forse la differenza potrebbe cambiare altrimenti se si selezionasse una pagina di righe (come 20 o 50) come per una tipica griglia di visualizzazione Web e si confrontasse un singolo LEFT JOIN con due query - selezionando 2 o 3 identificatori con alcuni criteri WHERE e quindi eseguendo l'altro SELEZIONA query con IN ().
JustAMartin,

Le colonne id e other_id sono indicizzate?
Aarish Ramesh,

11

La vera domanda è: questi record hanno una relazione uno a uno o una relazione uno a molti ?

Risposta TLDR:

Se uno a uno, usa JOINun'istruzione.

Se uno-a-molti, usa una (o molte) SELECTistruzioni con l'ottimizzazione del codice lato server.

Perché e come utilizzare SELECT per l'ottimizzazione

SELECT'ing (con più query invece di join) su un grande gruppo di record basato su una relazione uno-a-molti produce un'efficienza ottimale, in quanto JOINha un problema esponenziale di perdita di memoria. Prendi tutti i dati, quindi usa un linguaggio di scripting lato server per risolverli:

SELECT * FROM Address WHERE Personid IN(1,2,3);

risultati:

Address.id : 1            // First person and their address
Address.Personid : 1
Address.City : "Boston"

Address.id : 2            // First person's second address
Address.Personid : 1
Address.City : "New York"

Address.id : 3            // Second person's address
Address.Personid : 2
Address.City : "Barcelona"

Qui, sto ottenendo tutti i record, in una dichiarazione selezionata. Questo è meglio di JOIN, che sarebbe sempre un piccolo gruppo di questi record, uno alla volta, come un sub-componente di un altro query. Quindi lo analizzo con un codice lato server che assomiglia a ...

<?php
    foreach($addresses as $address) {
         $persons[$address['Personid']]->Address[] = $address;
    }
?>

Quando non usare JOIN per l'ottimizzazione

JOIN'ing un grande gruppo di record basato su una relazione uno a uno con un singolo record produce un'efficienza ottimale rispetto a più SELECTistruzioni, una dopo l'altra, che ottengono semplicemente il tipo di record successivo.

Ma JOINè inefficiente quando si ottengono record con una relazione uno-a-molti.

Esempio: il database Blog ha 3 tabelle di interesse, Blogpost, Tag e Comment.

SELECT * from BlogPost
LEFT JOIN Tag ON Tag.BlogPostid = BlogPost.id
LEFT JOIN Comment ON Comment.BlogPostid = BlogPost.id;

Se ci sono 1 post sul blog, 2 tag e 2 commenti, otterrai risultati come:

Row1: tag1, comment1,
Row2: tag1, comment2,
Row3: tag2, comment1,
Row4: tag2, comment2,

Notare come ogni record viene duplicato. Bene, quindi, 2 commenti e 2 tag sono 4 righe. Cosa succede se abbiamo 4 commenti e 4 tag? Non ottieni 8 righe - ottieni 16 righe:

Row1: tag1, comment1,
Row2: tag1, comment2,
Row3: tag1, comment3,
Row4: tag1, comment4,
Row5: tag2, comment1,
Row6: tag2, comment2,
Row7: tag2, comment3,
Row8: tag2, comment4,
Row9: tag3, comment1,
Row10: tag3, comment2,
Row11: tag3, comment3,
Row12: tag3, comment4,
Row13: tag4, comment1,
Row14: tag4, comment2,
Row15: tag4, comment3,
Row16: tag4, comment4,

Aggiungi più tabelle, più record, ecc. E il problema si gonfia rapidamente in centinaia di righe che sono tutte piene di dati prevalentemente ridondanti.

Quanto ti costano questi duplicati? Memoria (nel server SQL e il codice che tenta di rimuovere i duplicati) e risorse di rete (tra il server SQL e il server del codice).

Fonte: https://dev.mysql.com/doc/refman/8.0/en/nested-join-optimization.html ; https://dev.mysql.com/doc/workbench/en/wb-relationship-tools.html


Ti manca il punto. Non si tratta di uno a (uno | molti). Si tratta di stabilire se gli insiemi di righe hanno senso essere associati. Stai chiedendo due soli set di dati tangenzialmente correlati. Se stavi chiedendo commenti e, diciamo, le informazioni di contatto dei loro autori, questo ha più senso come join, anche se le persone possono presumibilmente scrivere più di un commento.
cHao,

@cHao: grazie per il tuo commento. La mia risposta sopra è un riassunto della documentazione MySQL trovata qui: dev.mysql.com/doc/workbench/en/wb-relationship-tools.html
HoldOffHunger

Questa non è la documentazione di MySQL. È la documentazione per un particolare strumento GUI per lavorare con i database MySQL. E non offre alcuna guida su quando i join sono (o non sono) appropriati.
cHao,

@cHao: Mi dispiace, intendevo la documentazione di MySQL (R) per MySQL WorkBench (TM), non MySQL Server (TM).
HoldOffHunger,

Pedanteria a parte, la rilevanza non è chiara. Entrambi menzionano relazioni uno a uno e uno a molti, ma è lì che finisce la comunanza. In entrambi i casi, il problema riguarda la relazione tra le serie di dati. Unisciti a due set non correlati, otterrai ogni combinazione dei due. Suddividi i dati correlati in più selezioni e ora hai fatto più query per vantaggi discutibili e hai iniziato a fare il lavoro di MySQL per questo.
cHao,

8

Costruisci query e join separati, quindi cronometra ognuno di essi: nulla aiuta più dei numeri del mondo reale.

Quindi, ancora meglio: aggiungi "EXPLAIN" all'inizio di ogni query. Questo ti dirà quante subquery sta usando MySQL per rispondere alla tua richiesta di dati e quante righe sono state scansionate per ogni query.


7

A seconda della complessità del database rispetto alla complessità dello sviluppatore, potrebbe essere più semplice eseguire molte chiamate SELECT.

Prova a eseguire alcune statistiche del database sia su JOIN che su SELECTS multipli. Verifica se nel tuo ambiente il JOIN è più veloce / più lento del SELECT.

Poi di nuovo, se cambiarlo in un JOIN significherebbe un giorno / settimana / mese extra di lavoro di sviluppo, rimarrei con più SELECT

Saluti,

BLT


5

Nella mia esperienza ho scoperto che di solito è più veloce eseguire diverse query, soprattutto quando si recuperano set di dati di grandi dimensioni.

Quando si interagisce con il database da un'altra applicazione, come PHP, c'è l'argomento di un viaggio sul server su molti.

Esistono altri modi per limitare il numero di viaggi effettuati sul server ed eseguire ancora più query che spesso non solo sono più veloci ma rendono anche più facile la lettura dell'applicazione, ad esempio mysqli_multi_query.

Non sono un principiante quando si tratta di SQL, penso che ci sia una tendenza per gli sviluppatori, in particolare i giovani, a passare molto tempo a cercare di scrivere join molto intelligenti perché sembrano intelligenti, mentre in realtà ci sono modi intelligenti per estrarre dati che sembrano semplice.

L'ultimo paragrafo era un'opinione personale, ma spero che questo aiuti. Sono d'accordo con gli altri, però, che dicono che dovresti fare un benchmark. Nessuno dei due approcci è un proiettile d'argento.


Sì, dovremmo anche tenere conto non solo delle query stesse ma anche dell'elaborazione dei dati all'interno dell'applicazione. Se si recuperano dati con join esterni, c'è una certa ridondanza (a volte può diventare davvero enorme) che deve essere risolta dall'app (di solito in alcune librerie ORM), quindi in sintesi la singola query SELECT con JOIN potrebbe consumare più CPU e tempo di due semplici SELECT
JustAMartin

4

Sia che si dovrebbe usare un join è prima di tutto sul fatto che un join ha un senso . Solo a quel punto le prestazioni possono essere prese in considerazione, poiché quasi tutti gli altri casi comporteranno prestazioni significativamente peggiori .

Le differenze di prestazioni saranno in gran parte legate alla relazione tra le informazioni per le quali stai eseguendo la query. I join funzionano e sono veloci quando i dati sono correlati e indicizzi correttamente le cose, ma spesso causano ridondanza e talvolta più risultati del necessario. E se i tuoi set di dati non sono direttamente correlati, incollarli in una singola query comporterà quello che viene chiamato un prodotto cartesiano (fondamentalmente, tutte le possibili combinazioni di righe), che non è quasi mai quello che vuoi.

Ciò è spesso causato da relazioni molti-a-uno-a-molti. Ad esempio, la risposta di HoldOffHunger menzionava una singola query per post, tag e commenti. I commenti sono correlati a un post, così come i tag ... ma i tag non sono correlati ai commenti.

+------------+     +---------+     +---------+
|  comment   |     |   post  |     |  tag    |
|------------|*   1|---------|1   *|---------|
| post_id    |-----| post_id |-----| post_id |
| comment_id |     | ...     |     | tag_id  |
| user_id    |     |         |     | ...     |
| ...        |     |         |     | ...     |
+------------+     +---------+     +---------+

In questo caso, è inequivocabilmente meglio per questo essere almeno due query separate. Se si tenta di unire tag e commenti, poiché non esiste una relazione diretta tra i due, si finisce con ogni possibile combinazione di tag e commento. many * many == manymany. A parte questo, poiché post e tag non sono correlati, puoi fare queste due query in parallelo, portando a potenziali guadagni.

Consideriamo uno scenario diverso, tuttavia: vuoi i commenti allegati a un post e le informazioni di contatto dei commentatori.

 +----------+     +------------+     +---------+
 |   user   |     |  comment   |     |   post  |
 |----------|1   *|------------|*   1|---------|
 | user_id  |-----| post_id    |-----| post_id |
 | username |     | user_id    |     | ...     |
 | ...      |     | ...        |     +---------+
 +----------+     +------------+

Qui è dove dovresti considerare un join. Oltre ad essere una query molto più naturale, la maggior parte dei sistemi di database (incluso MySQL) ha un sacco di persone intelligenti che fanno un sacco di duro lavoro per ottimizzare le query proprio come questa. Per query separate, poiché ogni query dipende dai risultati della precedente, le query non possono essere eseguite in parallelo e il tempo totale diventa non solo il tempo di esecuzione effettivo delle query, ma anche il tempo impiegato per recuperare i risultati, setacciare attraverso di essi per gli ID per la query successiva, collegando insieme le righe, ecc.


Se si recuperano molte colonne utente nel secondo scenario (e gli stessi utenti commentano più di una volta), ciò lascia comunque aperta la questione se siano recuperati meglio in una query separata.
Adrian Baker,

@AdrianBaker: Come ho detto, molte persone intelligenti stanno lavorando sodo. Se avessi ottimizzato il mio server SQL, la mia prima idea sarebbe quella di utilizzare la compressione, eliminando un'enorme quantità di ridondanza senza cambiare il codice molto a tutti. Le ottimizzazioni di livello successivo comprendono la riorganizzazione del risultato in tabelle e l'invio di tali insieme a tuple di ID riga, che la libreria client potrebbe quindi assemblare facilmente sul lato, se necessario.
cHao,

Entrambe queste ottimizzazioni potrebbero fare miracoli con un join per ridurre o addirittura eliminare la ridondanza, ma non c'è molto che possa aiutare con le query intrinsecamente seriali che dovresti fare per recuperare i record correlati.
cHao,

3

Sarà più veloce in termini di produttività? Probabilmente. Ma potenzialmente blocca anche più oggetti di database alla volta (a seconda del database e dello schema) e quindi riduce la concorrenza. Nella mia esperienza, le persone sono spesso fuorviate dall'argomento "meno round-trip del database" quando in realtà sulla maggior parte dei sistemi OLTP in cui il database si trova sulla stessa LAN, il vero collo di bottiglia è raramente la rete.



1

Esistono diversi fattori, il che significa che non esiste una risposta binaria. La domanda su cosa sia meglio per le prestazioni dipende dal tuo ambiente. A proposito, se la tua singola selezione con un identificatore non è inferiore al secondo, qualcosa potrebbe non essere corretto nella tua configurazione.

La vera domanda da porsi è come si desidera accedere ai dati. La selezione singola supporta il late-binding. Ad esempio, se si desidera solo informazioni sui dipendenti, è possibile selezionare dalla tabella Employees. Le relazioni di chiave esterna possono essere utilizzate per recuperare risorse correlate in un secondo momento e secondo necessità. Le selezioni avranno già una chiave a cui puntare, quindi dovrebbero essere estremamente veloci e dovrai solo recuperare ciò di cui hai bisogno. La latenza di rete deve essere sempre presa in considerazione.

I join recupereranno tutti i dati contemporaneamente. Se stai generando un rapporto o popolando una griglia, questo potrebbe essere esattamente quello che desideri. In questo scenario, i join compilati e optomizzati saranno semplicemente più veloci rispetto alle selezioni singole. Ricorda, i join ad hoc potrebbero non essere così veloci: dovresti compilarli (in un proc memorizzato). La risposta rapida dipende dal piano di esecuzione, che specifica in dettaglio quali passi vengono eseguiti dal DBMS per recuperare i dati.


0

Sì, una query che utilizza JOINS sarebbe più veloce. Sebbene senza conoscere le relazioni delle tabelle di cui si sta eseguendo la query, le dimensioni del set di dati o dove si trovano le chiavi primarie, è quasi impossibile dire quanto più velocemente.

Perché non testare entrambi gli scenari, quindi lo saprai per certo ...

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.