0Pricing
SQL Interview Prep · Lezione

Algoritmi di join: Nested Loop, Hash, Merge

Come viene eseguito ciascun join e quando rappresenta la scelta corretta.

Algoritmi di join: Nested Loop, Hash, Merge è una lezione SQL Interview Prep gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento SQL Interview Prep, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso SQL Interview Prep include 4 lezioni in totale.

I join sono algoritmi, non solo sintassi

Lei conosce già INNER JOIN come sintassi. Nei colloqui per ruoli senior, gli intervistatori chiedono come il database esegue fisicamente un join. Esistono tre algoritmi:

  • Nested Loop Join
  • Hash Join
  • Merge Join (sort-merge)

Il tipo logico di join (INNER, LEFT) è indipendente dall'algoritmo. Il planner sceglie l'algoritmo in base alle dimensioni delle tabelle, agli indici e all'ordinamento. Capire quando ciascuno offre i risultati migliori è il fulcro di questa lezione.

Nested Loop Join

Il Nested Loop è il metodo più semplice: per ogni riga della tabella esterna, esamina la tabella interna alla ricerca delle corrispondenze. In pseudocodice, sono due cicli, uno annidato nell'altro.

In modo ingenuo, ha complessità O(outer * inner), quindi è pessimo per le tabelle grandi. Tuttavia, diventa eccellente quando il lato inner ha un indice sulla chiave di join: ogni riga del lato esterno attiva una ricerca rapida nell'indice invece di una scansione completa del lato interno.

È il metodo preferito dal planner quando la tabella esterna è piccola e la colonna di join della tabella interna è indicizzata.

Nested Loop  (cost=0.42..120.5 rows=15 width=72)
  ->  Seq Scan on customers c  (rows=3)
  ->  Index Scan using idx_orders_cust on orders o
        Index Cond: (o.customer_id = c.id)
        (loops=3)

Lettura dei cicli in un Nested Loop

L'indizio rivelatore di un nested loop è la presenza di loops sul nodo interno. L'esempio mostra loops=3 perché il lato esterno ha prodotto 3 righe, quindi la scansione dell'indice interno è stata eseguita 3 volte.

Il problema emerge quando il lato esterno è grande. Se produce 2 milioni di righe, il lato interno viene eseguito 2 milioni di volte. Anche una ricerca rapida da 0.01ms diventa così un'attesa di 20 secondi.

Nei colloqui, segnali qualsiasi nested loop con un valore elevato di loops su una tabella interna priva di un buon indice: quella è la query lenta.

Hash Join

Il Hash Join gestisce bene le tabelle grandi non ordinate. Funziona in due fasi:

  • Build: legge la tabella più piccola e la carica in una hash table in memoria, indicizzata sulla colonna di join.
  • Probe: esegue la scansione della tabella più grande; per ogni riga, calcola l'hash della chiave di join e lo cerca nella hash table.

Ogni tabella viene letta una sola volta, con una complessità approssimativa O(outer + inner). Non richiede né indici né dati già ordinati, per questo prevale nei join analitici di grandi dimensioni basati su condizioni di uguaglianza.

Hash Join  (cost=18.0..520.0 rows=900 width=72)
  Hash Cond: (o.customer_id = c.id)
  ->  Seq Scan on orders o  (rows=100000)
  ->  Hash  (rows=500)
        ->  Seq Scan on customers c  (rows=500)

Limiti dell'Hash Join

Ci sono due aspetti da citare assolutamente sugli hash join:

  • Funzionano solo con condizioni di join basate sull'uguaglianza (a.id = b.id). Una condizione su un intervallo come a.x < b.y non può usare un hash join.
  • Il lato di build deve entrare in work_mem. In caso contrario, Postgres scrive i batch su disco (si vedranno Batches: > 1 e l'uso del disco), rallentando notevolmente il join.

Quindi, un hash join con un lato di build enorme e un valore di work_mem molto basso è un vero problema di prestazioni da segnalare.

Hash  (actual rows=2000000 loops=1)
  Buckets: 65536  Batches: 16  Memory Usage: 4096kB

Merge Join

Il Merge Join (sort-merge) richiede che entrambi gli input siano ordinati in base alla chiave di join. Poi li percorre in parallelo, come quando si fondono due liste ordinate, facendo avanzare il puntatore che si trova più indietro.

È efficiente quando gli input sono già ordinati, per esempio perché provengono direttamente da un indice nell'ordine della chiave: in questo caso non serve alcun passaggio di ordinamento. Supporta inoltre i join su intervalli e sulle disuguaglianze, a differenza dell'hash join.

Se gli input non sono preordinati, il planner aggiunge nodi Sort espliciti e il costo dell'ordinamento può rendere più conveniente un hash join.

Merge Join  (cost=0.85..210.0 rows=900 width=72)
  Merge Cond: (o.customer_id = c.id)
  ->  Index Scan using idx_orders_cust on orders o
  ->  Index Scan using customers_pkey on customers c

Schema rapido per scegliere

Memorizzi quando ciascun algoritmo offre i risultati migliori:

  • Nested Loop, tabella esterna piccola e chiave di join interna indicizzata; è anche l'unica opzione per i join basati su disuguaglianze quando l'input non è ordinato.
  • Hash Join, tabelle grandi non ordinate unite con una condizione di uguaglianza; non servono indici.
  • Merge Join, entrambi gli input già ordinati in base alla chiave, spesso tramite indici, oppure join su intervalli; ideale per insiemi molto grandi e già ordinati.

Il planner stima il costo di ciascuna alternativa e sceglie quella meno costosa in base alle stime del numero di righe.

Costi di memoria e ordinamento

L'uso delle risorse varia notevolmente, ed è un aspetto su cui gli intervistatori insistono:

  • Nested Loop, usa poca memoria; il costo è dominato dalle ricerche ripetute nel lato interno.
  • Hash Join, richiede memoria per la hash table e scrive su disco se questa è troppo grande.
  • Merge Join, è economico da eseguire ma costoso se prima deve ordinare i dati; anche gli ordinamenti usano work_mem e possono scrivere su disco.

Aumentare work_mem può quindi trasformare un hash join o un ordinamento lento perché scrive su disco in un'operazione eseguita interamente in memoria: è una risposta concreta quando si parla di ottimizzazione.

Perché un Nested Loop ha avuto esito negativo

Scenario classico: una query era veloce in sviluppo, ma lenta in produzione. Il piano mostra un Nested Loop con loops=3000000.

Il planner ha sottostimato il numero di righe del lato esterno (le statistiche obsolete indicavano 3 righe, mentre nella realtà erano 3 milioni), quindi ha scelto un nested loop. Con statistiche accurate avrebbe scelto un hash join.

La risposta da dare al colloquio è: esegua ANALYZE per correggere la stima; a quel punto il planner passerà a un hash join e la query diventerà molto più veloce.

Nested Loop  (cost=0.42..50.0 rows=3 width=72)
  ->  Seq Scan on big_outer  (actual rows=3000000 loops=1)
  ->  Index Scan on inner_t  (actual rows=1 loops=3000000)

Come influenzare la scelta

In genere non dovrebbe forzare gli algoritmi, ma può farlo nei test per confrontarli. Postgres espone impostazioni per attivare o disattivare ciascun metodo:

SET enable_nestloop = off; e impostazioni analoghe per enable_hashjoin e enable_mergejoin. Ne disattivi una, esegua di nuovo EXPLAIN ANALYZE e osservi se l'alternativa è effettivamente più veloce.

Le correzioni appropriate restano: statistiche aggiornate, indici adeguati, un valore sufficiente di work_mem e predicati selettivi. La forzatura serve solo per la diagnosi.

SET enable_nestloop = off;
EXPLAIN ANALYZE
SELECT * FROM orders o JOIN customers c ON o.customer_id = c.id;
SET enable_nestloop = on;

Riepilogo dei join su larga scala

Applichiamo tutto a un carico analitico che unisce due grandi tabelle dei fatti e delle dimensioni tramite un id:

  • Se la dimension table entra in memoria, è probabile un Hash Join, spesso la scelta migliore.
  • Se entrambe le tabelle arrivano già ordinate dagli indici, un Merge Join può evitare la fase di build dell'hash.
  • In questo caso, un Nested Loop sarebbe un segnale d'allarme, solitamente causato da una stima errata.

Leggere quale algoritmo ha scelto il planner e valutare se avrebbe dovuto sceglierne un altro è esattamente la competenza senior che queste domande intendono verificare.

Verifica rapida

Si uniscono due tabelle grandi e non ordinate con una condizione di uguaglianza a.id = b.id; nessuna delle due ha un indice utile e le statistiche sono accurate. Quale algoritmo di join sceglierà con maggiore probabilità il planner?

Ripasso

I tre algoritmi di join:

  • Nested Loop, una riga esterna alla volta con una ricerca interna; ottimo con un lato esterno piccolo e una chiave interna indicizzata, ma rischioso quando loops è enorme.
  • Hash Join, costruzione e ricerca; ideale per join grandi, non ordinati e basati sull'uguaglianza, limitato alle uguaglianze e vincolato da work_mem.
  • Merge Join, percorre in parallelo input ordinati; ideale quando i dati sono già ordinati o per i join su intervalli.

Il planner sceglie in base a costi e statistiche. Un nested loop sorprendente con un numero enorme di cicli indica quasi sempre una stima errata delle righe: corregga le statistiche.

Domande Frequenti

La lezione «Algoritmi di join: Nested Loop, Hash, Merge» è gratuita?

Sì — il testo completo di «Algoritmi di join: Nested Loop, Hash, Merge» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso SQL Interview Prep, passa a CoddyKit PRO. Il corso SQL Interview Prep include 4 lezioni in totale.

Cosa imparerò in «Algoritmi di join: Nested Loop, Hash, Merge»?

Come viene eseguito ciascun join e quando rappresenta la scelta corretta. Eserciti SQL Interview Prep con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare SQL Interview Prep?

Non è richiesta alcuna esperienza precedente. SQL Interview Prep su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Algoritmi di join: Nested Loop, Hash, Merge»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione SQL Interview Prep?

Sì. Ogni lezione SQL Interview Prep include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Leggere un piano EXPLAIN
  2. Seq Scan, Index Scan e Index-Only
  3. Algoritmi di join: Nested Loop, Hash, Merge
  4. Individuare e risolvere le query lente
← Torna a SQL Interview Prep