JOIN explícitas #
Es posible controlar el planificador de consultas hasta cierto punto utilizando la sintaxis de unión (JOIN) explícita.
Para entender por qué esto es importante, primero necesitamos un poco de contexto.
En una consulta de unión simple, como:
SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;
el planificador es libre de unir las tablas dadas en cualquier orden. Por ejemplo, podría generar un plan de consulta que una A con B,
utilizando la condición WHERE a.id = b.id, y luego una C a esta tabla resultante, utilizando la otra
condición WHERE. O podría unir B con C y luego unir A a ese resultado. O podría unir A con C y luego unirlos con B —
pero eso sería ineficiente, ya que se tendría que formar el producto cartesiano completo de A y C al no haber ninguna condición aplicable en
la cláusula WHERE que permita optimizar la unión. (Todas las uniones en el ejecutor de
PostgreSQL ocurren entre dos tablas de entrada, por lo que es necesario construir el resultado de una de estas formas).
El punto importante es que estas diferentes posibilidades de unión dan resultados semánticamente equivalentes pero podrían tener costes de
ejecución enormemente diferentes. Por lo tanto, el planificador explorará todas ellas para intentar encontrar el plan de consulta más eficiente.
Cuando una consulta solo involucra dos o tres tablas, no hay muchos órdenes de unión por los que preocuparse. Pero el número de órdenes de unión posibles crece exponencialmente a medida que aumenta el número de tablas. Más allá de unas diez tablas de entrada ya no resulta práctico realizar una búsqueda exhaustiva de todas las posibilidades, e incluso para seis o siete tablas la planificación podría tardar un tiempo bastante molesto. Cuando hay demasiadas tablas de entrada, el planificador de PostgreSQL cambiará de una búsqueda exhaustiva a una búsqueda probabilística genética a través de un número limitado de posibilidades. (El umbral de cambio se establece mediante el parámetro de tiempo de ejecución geqo_threshold). La búsqueda genética requiere menos tiempo, pero no encontrará necesariamente el mejor plan posible.
Cuando la consulta involucra uniones externas, el planificador tiene menos libertad que para las uniones internas simples. Por ejemplo, considera:
SELECT * FROM a LEFT JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
Aunque las restricciones de esta consulta son superficialmente similares al ejemplo anterior, la semántica es diferente porque se debe emitir una fila por cada fila de A que no tenga ninguna fila coincidente en la unión de B y C. Por lo tanto, el planificador no tiene elección de orden de unión aquí: debe unir B con C y luego unir A a ese resultado. En consecuencia, esta consulta tarda menos tiempo en planificarse que la anterior. En otros casos, el planificador podría determinar que más de un orden de unión es seguro. Por ejemplo, dado:
SELECT * FROM a LEFT JOIN b ON (a.bid = b.id) LEFT JOIN c ON (a.cid = c.id);
es válido unir A con B o con C primero. Actualmente, solo FULL JOIN restringe por completo el orden de la unión. La mayoría de
los casos prácticos que involucran LEFT JOIN o RIGHT JOIN se pueden reorganizar hasta cierto punto.
La sintaxis explícita de unión interna (INNER JOIN, CROSS JOIN o simplemente JOIN sin
adornos) es semánticamente idéntica a listar las relaciones de entrada en FROM, por lo que no restringe el orden de la unión.
Aunque la mayoría de los tipos de JOIN no restringen por completo el orden de la unión, es posible indicar al planificador de
consultas de PostgreSQL que trate todas las cláusulas JOIN como si restringieran el orden de todos modos.
Por ejemplo, estas tres consultas son lógicamente equivalentes:
SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id; SELECT * FROM a CROSS JOIN b CROSS JOIN c WHERE a.id = b.id AND b.ref = c.id; SELECT * FROM a JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
But if we tell the planner to honor the JOIN order,
the second and third take less time to plan than the first. This effect
is not worth worrying about for only three tables, but it can be a
lifesaver with many tables.
To force the planner to follow the join order laid out by explicit
JOINs,
set the join_collapse_limit run-time parameter to 1.
(Other possible values are discussed below.)
You do not need to constrain the join order completely in order to
cut search time, because it's OK to use JOIN operators
within items of a plain FROM list. For example, consider:
SELECT * FROM a CROSS JOIN b, c, d, e WHERE ...;
With join_collapse_limit = 1, this
forces the planner to join A to B before joining them to other tables,
but doesn't constrain its choices otherwise. In this example, the
number of possible join orders is reduced by a factor of 5.
Restringir la búsqueda del planificador de esta manera es una técnica útil
tanto para reducir el tiempo de planificación como para dirigir al
planificador hacia un buen plan de consulta. Si el planificador elige un mal orden de unión
por defecto, puedes obligarlo a elegir un orden mejor a través de la sintaxis JOIN
— asumiendo que conozcas un orden mejor, claro está. Se recomienda experimentar.
Un problema estrechamente relacionado que afecta al tiempo de planificación es el colapso de las subconsultas en su consulta padre. Por ejemplo, considera:
SELECT *
FROM x, y,
(SELECT * FROM a, b, c WHERE algo) AS ss
WHERE otra_cosa;
Esta situación podría surgir del uso de una vista que contiene una unión; la regla SELECT de la
vista se insertará en lugar de la referencia a la vista, produciendo una consulta muy similar a la anterior.
Normalmente, el planificador intentará colapsar la subconsulta en la padre, obteniendo:
SELECT * FROM x, y, a, b, c WHERE algo AND otra_cosa;
Esto suele dar como resultado un plan mejor que planificar la subconsulta por separado. (Por ejemplo, las condiciones
WHERE externas podrían ser tales que unir X con A primero elimine many rows of A, evitando así
la necesidad de formar la salida lógica completa de la subconsulta). Pero al mismo tiempo, hemos incrementado el
tiempo de planificación; aquí tenemos un problema de unión de cinco tablas reemplazando dos problemas de unión de
tres tablas independientes. Debido al crecimiento exponencial del número de posibilidades, esto hace una gran diferencia.
El planificador intenta evitar quedarse atascado en problemas enormes de búsqueda de uniones no colapsando una subconsulta
si el resultado en la consulta padre superaría los from_collapse_limit elementos FROM.
Puedes equilibrar el tiempo de planificación frente a la calidad del plan ajustando este parámetro de tiempo de
ejecución hacia arriba o hacia abajo.
from_collapse_limit y join_collapse_limit tienen nombres similares porque
hacen casi lo mismo: uno controla cuándo el planificador “aplana” las subconsultas y el otro controla
cuándo aplana las uniones explícitas. Típicamente establecerías join_collapse_limit igual a
from_collapse_limit (para que las uniones explícitas y las subconsultas actúen de manera similar) o
establecerías join_collapse_limit en 1 (si deseas controlar el orden de unión con uniones explícitas).
Pero podrías configurarlos de forma diferente si estás intentando ajustar con precisión el equilibrio entre el tiempo
de planificación y el de ejecución.