Documentação do PostgreSQL 8.0.0 | ||||
---|---|---|---|---|
Anterior | Início | Capítulo 41. Visão geral da estrutura interna do PostgreSQL | Fim | Próxima |
A tarefa do planejador/otimizador é criar um plano de execução ótimo. Um dado comando SQL (e, portanto, uma árvore de comando) pode, na verdade, ser executada de várias maneiras diferentes, cada uma das quais produzindo o mesmo conjunto de resultados. Se for computacionalmente praticável, o otimizador de comandos examina cada um dos planos de execução possíveis para, no fim, selecionar o plano de execução que espera ser o mais rápido.
Nota: Em algumas situações, o exame de todas as formas pelas quais um comando pode ser executado leva a um consumo excessivo de tempo e de espaço em memória. Em particular, estas situações ocorrem quando se executa comandos que envolvem um grande número de operações de junção. Para ser possível determinar um plano de comando razoável (não o ótimo), em um espaço de tempo razoável, o PostgreSQL utiliza o Genetic Query Optimizer.
Na verdade, o procedimento de procura do planejador trabalha com estruturas de dados chamadas de caminhos (paths), que são simplesmente representações reduzidas dos planos, contendo somente as informações necessárias para o planejador tomar suas decisões. Após ser determinado o caminho mais barato, é construída a árvore de plano pronta para ser passada para o executor. Esta árvore representa o plano de execução desejado no nível de detalhamento suficiente para o executor processá-la. No restante desta seção será ignorada a distinção entre caminhos e planos.
O planejador/otimizador inicia gerando planos para varrer individualmente cada relação (tabela) utilizada no comando. Os planos possíveis são determinados pelos índices disponíveis em cada relação. Sempre existe a possibilidade de realizar a varredura seqüencial da relação, portanto o plano para varredura seqüencial é sempre criado. Assumindo que haja um índice definido em uma relação (por exemplo um índice árvore-B), e o comando contenha a restrição relação.atributo OPR constante, se acontecer de relação.atributo corresponder à chave do índice árvore-B, e OPR for um dos operadores listados na classe de operadores do índice, é criado um outro plano utilizando o índice árvore-B para varrer a relação. Se existirem outros índices presentes, e a restrição no comando corresponder à chave do índice, serão levados em consideração outros planos.
Após terem sido encontrados todos os planos viáveis para varrer uma única relação, são criados planos para juntar as relações. O planejador/otimizador considera preferencialmente junções entre quaisquer duas relações para as quais existe uma cláusula de junção correspondente na qualificação do WHERE (ou seja, para as quais existe uma restrição do tipo WHERE rel1.atrib1=rel2.atrib2). Os pares de junção sem cláusula de junção são considerados somente quando não há outra escolha, ou seja, uma determinada relação não tem disponível cláusula de junção com qualquer outra relação. São gerados todos os planos possíveis para cada par de junção considerado pelo planejador/otimizador. As três estratégias possíveis são:
junção de laço aninhado: A relação da direita é varrida uma vez para cada linha encontrada na relação da esquerda. Esta estratégia é fácil de ser implementada, mas pode consumir muito tempo (Entretanto, se a relação da direita puder ser varrida através de uma varredura de índice, esta é uma boa estratégia. É possível utilizar valores da linha corrente da relação da esquerda como chaves para a varredura de índice da relação da direita).
junção por classificação e mesclagem: Cada relação é classificada pelo atributo de junção antes do início da junção. As duas relações são varridas em paralelo, e as linhas correspondentes são combinadas para formar as linhas juntadas. Este tipo de junção é mais atrativo, porque só é necessário varrer cada relação uma vez. A classificação requerida pode ser obtida por um passo explícito de classificação, ou varrendo a relação na ordem apropriada utilizando um índice na chave de junção.
junção hash: Primeiro, a relação da direita é varrida e carregada numa tabela de hash utilizando os atributos de junção como chaves de hash. Em seguida, a relação da esquerda é varrida e os valores apropriados de cada linha encontrada são utilizados como chave de hash para localizar as linhas correspondentes na tabela.
Quando o comando envolve mais de duas relações, o resultado final deve ser construído através de uma árvore de passos de junção, cada um com duas entradas. O planejador examina as diferentes possibilidades de seqüência de junção para descobrir a mais barata.
A árvore do plano pronta consiste de varreduras seqüenciais ou de índice das relações base, mais nodos de junção de laço aninhado, mesclagem e hash conforme necessário, mais os passos auxiliares necessários, como nodos de classificação ou nodos de cálculo de funções de agregação. A maioria destes tipos de nodos de plano possuem a capacidade adicional de realizar seleção (desprezar as linhas que não correspondem à condição booleana especificada) e projeção (cálculo de um conjunto de colunas derivadas baseado em valores de coluna fornecidos, ou seja, avaliação de expressões escalares onde for necessário). Uma das responsabilidades do planejador é anexar as condições de seleção da cláusula WHERE e os cálculos das expressões de saída requeridos ao nodo mais apropriado da árvore do plano.