JOIN explícitas #
É possível controlar o planejador de consultas até certo ponto usando
a sintaxe explícita JOIN. Para ver por que isto
importa, primeiro precisamos de algum conhecimento.
Em uma consulta de junção simples, como:
SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;
o planejador é livre para juntar as tabelas em qualquer ordem.
Por exemplo, o planejador pode gerar um plano de consulta que une
“A” a “B”, usando a condição
WHERE a.id = b.id e, em seguida,
unir “C” a esta tabela unida, usando a outra condição
WHERE.
Ou poderia juntar “B” a “C” e depois juntar
“A” a este resultado.
Ou poderia juntar “A” a “C” e depois
juntá-los com “B” — mas isto seria ineficiente,
porque o produto cartesiano completo de “A” e
“C” teria que ser formado, não havendo condição aplicável
na cláusula WHERE para permitir a otimização da junção.
(Todas as junções no executor do PostgreSQL
acontecem entre duas tabelas de entrada, então é necessário construir
o resultado em uma ou outra dessas formas.)
O ponto importante é que estas diferentes possibilidades de junção
fornecem resultados semanticamente equivalentes, mas podem ter custos
de execução muito diferentes.
Portanto, o planejador irá explorar todos eles para tentar encontrar
o plano de consulta mais eficiente.
Quando uma consulta envolve apenas duas ou três tabelas, não há muitos pedidos de junção com os quais se preocupar. Mas o número de pedidos de junção possíveis cresce exponencialmente à medida que o número de tabelas se expande. Além de dez ou mais tabelas de entrada, não é mais prático fazer uma busca exaustiva de todas as possibilidades, e mesmo para seis ou sete tabelas o planejamento pode levar um tempo irritantemente longo. Quando houver muitas tabelas de entrada, o planejador do PostgreSQL mudará da procura exaustiva para uma procura probabilística genética através de um número limitado de possibilidades. (O limite de alternância é definido pelo parâmetro de tempo de execução geqo_threshold.) A busca genética levará menos tempo, mas não encontrará necessariamente o melhor plano possível.
Quando a consulta envolve junções externas, o planejador tem menos liberdade do que para junções simples (internas). Por exemplo, considere:
SELECT * FROM a LEFT JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
Embora as restrições dessa consulta sejam superficialmente semelhantes ao exemplo anterior, a semântica é diferente, porque uma linha deve ser emitida para cada linha de “A” que não possui linha correspondente na junção de “B” e “C”. Portanto, o planejador não tem escolha de ordem de junção aqui: deve unir “B” a “C” e então unir “A” a este resultado. Assim, esta consulta leva menos tempo para planejar do que a consulta anterior. Em outros casos, o planejador pode determinar que mais de uma ordem de associação é segura. Por exemplo, dado:
SELECT * FROM a LEFT JOIN b ON (a.bid = b.id) LEFT JOIN c ON (a.cid = c.id);
é válido juntar “A” a “B” ou
“C” primeiro.
No momento, apenas FULL JOIN restringe
completamente a ordem de junção.
A maioria dos casos práticos envolvendo LEFT JOIN
ou RIGHT JOIN podem ser reorganizados até certo ponto.
A sintaxe de junção interna explícita (INNER JOIN,
CROSS JOIN, ou JOIN sem adornos),
é semanticamente idêntica a listar as relações de entrada em
FROM, portanto, não restringe a ordem de junção.
Mesmo que a maioria dos tipos de JOIN não restrinja
completamente a ordem de junção, é possível instruir o planejador de
consulta do PostgreSQL para tratar todas
as cláusulas JOIN como restringindo a ordem de
junção de qualquer maneira.
Por exemplo, estas três consultas são logicamente 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);
Mas se dissermos ao planejador para honrar a ordem de
JOIN, o segundo e o terceiro levarão menos tempo
para planejar do que o primeiro.
Não vale a pena se preocupar com este efeito para apenas três tabelas,
mas pode ser um salva-vidas com muitas tabelas.
Para forçar o planejador a seguir a ordem de junção estabelecida por
JOINs explícitos, deve ser definido o parâmetro de
tempo de execução join_collapse_limit como 1.
(Outros valores possíveis são discutidos abaixo.)
Não é necessário restringir completamente a ordem de junção para
reduzir o tempo de procura, por não haver problema em usar operadores
JOIN dentro dos itens de uma lista
FROM simples. Por exemplo, considere:
SELECT * FROM a CROSS JOIN b, c, d, e WHERE ...;
Com join_collapse_limit = 1, isto força o
planejador a unir “A” a “B” antes de uni-los
a outras tabelas, mas não restringe suas escolhas de outra forma.
Neste exemplo, o número de pedidos de junção possíveis é reduzido por
um fator de 5.
Restringir a procura do planejador dessa maneira é uma técnica útil
tanto para reduzir o tempo de planejamento, quanto para direcionar
o planejador para um bom plano de consulta.
Se o planejador escolher uma ordem de junção incorreta por padrão,
poderá ser forçado a escolher uma ordem melhor por meio da sintaxe
JOIN — ou seja, supondo que se conheça uma
ordem melhor. A experimentação é recomendada.
Um problema intimamente relacionado que afeta o tempo de planejamento é o colapso da subconsulta em sua consulta mãe. Por exemplo, considere:
SELECT *
FROM x, y,
(SELECT * FROM a, b, c WHERE algumacoisa) AS ss
WHERE algumaoutracoisa;
Esta situação pode surgir do uso de uma visão contendo uma junção;
a regra SELECT da visão será inserida no lugar da
referência da visão, produzindo uma consulta muito parecida com a
vista acima.
Normalmente, o planejador tentará colapsar a subconsulta na consulta
mãe, produzindo:
SELECT * FROM x, y, a, b, c WHERE algumacoisa AND algumaoutracoisa;
Isto geralmente fornece um plano melhor do que planejar a
subconsulta em separado.
(Por exemplo, as condições externas WHERE podem
ser tais que unir “X” a “A” primeiro elimina
muitas linhas de “A”, evitando assim a necessidade de
gerar a saída lógica completa da subconsulta.)
Mas, ao mesmo tempo, aumenta o tempo de planejamento; aqui temos um
problema de junção de cinco tabelas, substituindo dois problemas de
junção de três tabelas separados.
Devido ao aumento exponencial do número de possibilidades,
algumaoutracoisa faz uma grande diferença.
O planejador tenta evitar ficar preso a problemas de procura em
muitas junções, não colapsando a subconsulta se surgirem mais de
from_collapse_limit itens na cláusula
FROM da consulta mãe.
Pode-se compensar o tempo de planejamento pela qualidade do plano,
ajustando este parâmetro de tempo de execução para cima ou para baixo.
from_collapse_limit e
join_collapse_limit
têm nomes semelhantes, porque fazem quase a mesma coisa: um controla
quando o planejador irá “achatar” as subconsultas, e o
outro controla quando ele irá “achatar” as junções
explícitas.
Normalmente se define join_collapse_limit igual a
from_collapse_limit
(para que junções e subconsultas explícitas atuem de maneira semelhante),
ou se define join_collapse_limit igual a 1
(se for desejado controlar a ordem de junção com junções explícitas).
Mas podem ser definidos de forma diferente, quando se tenta ajustar
o equilíbrio entre o tempo de planejamento e o tempo de execução.