Lección 5.9: Algoritmos de Join — Cómo el Base de Datos Ejecuta Joins
En lecciones anteriores, escribimos joins en SQL y nos enfocamos en qué datos devuelven. Pero, ¿cómo ejecuta realmente el base de datos un join en su interior? Entender los algoritmos físicos que utiliza el motor es clave para escribir consultas que funcionen bien en conjuntos de datos grandes.
Los tres principales algoritmos de join son:
- Nested Loop Join
- Hash Join
- Merge Join (también llamado Sort-Merge Join)
El planificador de consultas elige uno automáticamente en función del tamaño de las tablas, los índices disponibles y la memoria. No podemos forzar un algoritmo específico en SQL estándar, pero entender las compensaciones nos permite escribir consultas y crear índices que guíen al planificador hacia la mejor opción.

1. Nested Loop Join
Cómo Funciona
El Nested Loop Join es el algoritmo más simple. El base de datos selecciona una tabla como la tabla externa (controladora) y la otra como la tabla interna. Luego itera sobre cada fila en la tabla externa y, para cada fila, busca coincidencias en la tabla interna — esencialmente dos bucles for anidados.
Pseudo-código conceptual:
for each row R1 in outer_table:
for each row R2 in inner_table:
if R1.key = R2.key:
output(R1, R2)
Cuando existe un índice en la columna de join de la tabla interna, el escaneo interno se convierte en una búsqueda rápida de índice en lugar de un escaneo completo de la tabla. Esta variante se llama Index Nested Loop Join y es uno de los caminos de ejecución más eficientes posibles.
Cuándo lo Usa el Planificador
- La tabla externa (controladora) es pequeña.
- Existe un índice en la columna de join de la tabla interna.
- El join utiliza una condición de no igualdad (
<,>,BETWEEN) — Hash Join y Merge Join requieren igualdad, por lo que Nested Loop es la única opción en este caso.
Ventajas y Desventajas
| Nested Loop Join | |
|---|---|
| Ventajas | Funciona con cualquier condición de join, incluidas las condiciones de rango |
| Extremadamente rápido cuando la tabla externa es pequeña y la tabla interna está indexada | |
| Bajo uso de memoria — no es necesario construir estructuras de datos auxiliares | |
Comienza a devolver el primer resultado de inmediato (bueno para consultas LIMIT) | |
| Desventajas | Muy lento en tablas grandes sin índices — O(N × M) en el peor de los casos |
| El rendimiento se degrada rápidamente a medida que crecen los tamaños de ambas tablas |
2. Hash Join
Cómo Funciona
Un Hash Join funciona en dos fases:
- Fase de construcción: El base de datos lee la tabla más pequeña y construye una tabla hash en memoria clave en la columna de join.
- Fase de sondeo: El base de datos escanea la tabla más grande y, para cada fila, busca en la tabla hash para encontrar filas coincidentes.
Pseudo-código conceptual:
-- Fase de construcción
hash_table = {}
for each row R1 in smaller_table:
hash_table[ hash(R1.key) ].append(R1)
-- Fase de sondeo
for each row R2 in larger_table:
for each match in hash_table[ hash(R2.key) ]:
if R2.key = match.key:
output(R2, match)
Esto da una complejidad general de O(N + M) — lineal en ambos tamaños de tabla — lo que lo hace mucho más escalable que un Nested Loop Join sin índice.
Cuándo lo Usa el Planificador
- Uniendo dos tablas grandes en una condición de igualdad.
- No existe un índice útil en la(s) columna(s) de join.
- Hay suficiente memoria disponible para mantener la tabla hash (
work_memen PostgreSQL).
Ventajas y Desventajas
| Hash Join | |
|---|---|
| Ventajas | Muy eficiente para joins de tablas grandes — O(N + M) |
| No requiere índices en las columnas de join | |
| Maneja bien datos no ordenados y desordenados | |
| Desventajas | Requiere una condición de igualdad — no se puede usar para joins de rango |
| Intensivo en memoria: si la tabla hash no cabe en RAM, se vuelca en disco (mucho más lento) | |
| Mayor costo de inicio — debe construir la tabla hash antes de devolver cualquier resultado |
Nota: En PostgreSQL puedes controlar el presupuesto de memoria con la configuración work_mem. Aumentarlo reduce la posibilidad de volcamientos costosos en disco en grandes Hash Joins.
3. Merge Join (Sort-Merge Join)
Cómo Funciona
Un Merge Join requiere que ambas tablas de entrada estén ordenadas en la columna de join. Luego fusiona los dos flujos ordenados simultáneamente — muy parecido al paso final del clásico algoritmo Merge Sort — avanzando un puntero a través de cada flujo para encontrar coincidencias.
Pseudo-código conceptual:
sort outer_table by key -- se omite si se usa un índice ordenado
sort inner_table by key -- se omite si se usa un índice ordenado
p1 = inicio de outer_table
p2 = inicio de inner_table
while not end of either stream:
if outer[p1].key = inner[p2].key:
output matching rows and advance both pointers
elif outer[p1].key < inner[p2].key:
advance p1
else:
advance p2
La optimización crítica: si la tabla se puede escanear a través de un índice ordenado, el paso de ordenación es gratuito y Merge Join se convierte en uno de los algoritmos más eficientes disponibles.
Cuándo lo Usa el Planificador
- Ambas tablas son grandes y la condición de join es de igualdad.
- Ambas tablas ya están ordenadas, o ambas se pueden escanear a través de un índice ordenado.
- La consulta ya requiere
ORDER BYoGROUP BYen la columna de join (la ordenación ocurre de todos modos).
Ventajas y Desventajas
| Merge Join | |
|---|---|
| Ventajas | Muy eficiente para tablas grandes cuando los datos están preordenados o existe un índice ordenado |
Produce salida en orden ordenado, lo que puede eliminar un paso posterior de ORDER BY | |
| Uso de memoria constante y predecible | |
| Desventajas | Requiere una condición de igualdad únicamente |
| Costoso paso de ordenación explícito si ninguna tabla está preordenada y no hay índice disponible | |
| Menos flexible que Hash Join al tratar con datos completamente desordenados |
4. Elegir el Algoritmo Correcto
El planificador de consultas elige el algoritmo automáticamente. Tú influyes en su decisión indirectamente creando los índices correctos y ajustando la configuración de memoria.
| Escenario | Algoritmo Probable |
|---|---|
| Tabla externa pequeña + tabla interna indexada | Nested Loop Join |
| Dos tablas grandes, igualdad, sin índices útiles | Hash Join |
| Dos tablas grandes, igualdad, ambas ordenadas / indexadas en orden | Merge Join |
Condición de no igualdad (<, >, BETWEEN) | Nested Loop Join (única opción) |
Consejos prácticos:
- Crea índices en columnas de clave foránea que se unan con frecuencia — esto permite joins rápidos de Index Nested Loop y Merge.
- Si un Hash Join se está volcando en disco, considera aumentar
work_memo revisar si la consulta puede ser reestructurada. - Usa
EXPLAIN ANALYZEpara inspeccionar qué algoritmo eligió realmente el planificador y cuánto tiempo tomó cada paso:
EXPLAIN ANALYZE
SELECT a.first_name, a.last_name, f.title
FROM actor AS a
INNER JOIN film_actor AS fa ON a.actor_id = fa.actor_id
INNER JOIN film AS f ON fa.film_id = f.film_id;
Busca palabras clave como Hash Join, Merge Join o Nested Loop en el plan de salida para identificar el algoritmo elegido y su costo.
Conclusiones Clave de Esta Lección
- Nested Loop Join itera filas en bucles anidados — rápido para tablas pequeñas con índices de soporte, muy lento para tablas grandes sin índices; el único algoritmo que admite condiciones de no igualdad.
- Hash Join construye una tabla hash en memoria a partir de la tabla más pequeña y la sondea — eficiente para tablas grandes no indexadas unidas por igualdad, pero intensivo en memoria.
- Merge Join lee dos flujos preordenados simultáneamente — ideal cuando los datos ya están en orden (por ejemplo, a través de un índice) y el join es por igualdad; produce resultados en orden ordenado como un bono.
- Los tres algoritmos admiten joins de igualdad; solo Nested Loop también admite condiciones de rango.
- Tú influyes en la elección del planificador a través de índices, configuraciones de memoria (
work_mem) y estructura de consulta — no especificando el algoritmo en SQL. - Siempre usa
EXPLAIN ANALYZEpara verificar qué algoritmo se está utilizando realmente y dónde se está gastando el tiempo.