10.3. Controlando o planejador com cláusulas JOIN explícitas

A partir do PostgreSQL 7.1 é possível algum controle sobre o planejador de consultas utilizando a sintaxe do JOIN. Para saber por que isto tem importância, primeiro é necessário ter 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 está livre para fazer a junção das tabelas em qualquer ordem. Por exemplo, pode gerar um plano de consulta juntando A e B utilizando a condição a.id = b.id do WHERE e, depois, fazer a junção de C com a tabela juntada utilizando a outra condição WHERE. Poderia, também, juntar B e C e depois juntar A ao resultado. Ou, também, juntar A e C e depois juntar B --- mas isto não seria eficiente, uma vez que o produto Cartesiano completo de A e C deveria ser produzido, porque não existe nenhuma cláusula na condição WHERE que permita a otimização desta junção (Todas as junções no executor do PostgreSQL acontecem entre duas tabelas de entrada sendo, portanto, necessária a construção do resultado a partir de uma ou outra destas modalidades). O ponto a ser destacado é que estas diferentes possibilidades de junção produzem resultados semanticamente equivalentes, mas com custos de execução muito diferentes. Portanto, o planejador explora todas as possibilidades tentando encontrar o plano de consulta mais eficiente.

Quando uma consulta envolve apenas duas ou três tabelas não existem muitas possibilidades de junção com que se preocupar. Porém, o número de possibilidades de junção cresce exponencialmente quando o número de tabelas aumenta. Acima de dez tabelas de entrada não é mais prático efetuar uma procura exaustiva de todas as possibilidades, e mesmo para seis ou sete tabelas o planejamento pode levar um longo tempo. Quando existem muitas tabelas de entrada, o planejador do PostgreSQL pode alternar de uma procura exaustiva para uma procura probabilística genética com um número limitado de possibilidades (O ponto de mudança é definido pelo parâmetro em tempo de execução GEQO_THRESHOLD descrito no Guia do Administrador do PostgreSQL). A procura genética leva menos tempo, mas não encontra necessariamente o melhor plano possível.

Quando a consulta envolve junções externas, o planejador tem muito menos liberdade que com a junções comuns (internas). Por exemplo, considerando-se:

SELECT * FROM a LEFT JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);

Embora as restrições desta consulta sejam superficialmente semelhantes às do exemplo anterior, as semânticas são diferentes porque uma linha deve ser gerada para cada linha de A que não possua linha correspondente na junção de B com C. Portanto, o planejador não pode escolher a junção neste caso: deve juntar B e C e depois juntar A ao resultado. Portanto, esta consulta leva menos tempo para ser planejada que a consulta anterior.

O planejador de consultas do PostgreSQL trata todas as sintaxes explícitas de JOIN como restrições das possibilidades de junção, embora não seja logicamente necessário fazer esta restrição para junções internas. Portanto, embora todas as consultas abaixo produzam o mesmo resultado

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);

a segunda e a terceira levam menos tempo para serem planejadas que a primeira. Não há motivo para se preocupar com este efeito para duas ou três tabelas, mas pode ser de grande ajuda quando existem muitas tabelas.

Não é necessário eliminar totalmente as possibilidades de junção para diminuir o tempo de procura, porque é correto utilizar operadores JOIN no FROM comum. Por exemplo,

SELECT * FROM a CROSS JOIN b, c, d, e WHERE ...;

força o planejador juntar A e B antes de juntá-las às outras tabelas, mas não restringe as possibilidades além disto. Neste exemplo, o número de possibilidades possíveis de junção é reduzida por um fator de cinco.

Havendo um misto de junções internas e externas em uma consulta complexa, pode não ser desejado restringir a procura do planejador por uma boa ordem nas junções internas dentro da junção externa. Isto não pode ser feito diretamente por meio da sintaxe do JOIN, mas a limitação da sintaxe pode ser contornada utilizando subconsultas. No exemplo

SELECT * FROM d LEFT JOIN
        (SELECT * FROM a, b, c WHERE ...) AS ss
        ON (...);

a junção com D será a última etapa do plano da consulta, mas o planejador está livre para considerar as diversas possibilidades de junção entre A, B e C.

Restringir as procuras do planejador desta forma é uma técnica útil tanto para reduzir o tempo de planejamento quanto para direcionar o planejador para um bom plano. Se o planejador escolher uma ordem de junção ruim por padrão, pode ser forçado a escolher uma ordem melhor por meio da sintaxe do JOIN --- assumindo o conhecimento uma ordem melhor. Recomenda-se a realização de experiências.