Banco de Dados I
2007
Módulo VI: Processamento e
Otimização de Consultas
(Aulas 1-5)
Clodis Boscarioli
Agenda:
O Processador de Consultas:
Conceitos Principais.
Algoritmos usados para implementar operações
algébricas;
Otimização Baseada em Custo;
Otimização Heurística;
Comentários sobre otimização no PostgreSQL.
Visão Geral
de um SGBD
Processador
de consultas
Usuários
navegantes
Programadores Usuários
Administrade aplicações sofisticados dores de BD
Interface com Programas de
aplicações
aplicações
Consultas
(queries)
Pré-compilador
de comandos
DML
Programas de
aplicações em
código objeto
Usuários
Esquema de
Banco de Dados
Compilador
DML
Interpretador
DDL
Componentes de execução
de consultas
SGBD
Gerenciador
de memória
Gerenciador
de transações
Gerenciador
de buffer
Gerenciador
de arquivos
Armazenamento
em disco
Índices
Arquivos de
dados
Dados
estatísticos
Dicionário
de dados
BD
Processamento de Consultas
Processar consultas envolve:
Traduzir consultas expressas em linguagens
de alto nível (como SQL) em expressões que
podem ser implementadas no nível físico do
sistema de banco de dados (nível de tabelas);
Otimizar a expressão destas consultas;
Avaliar a base de dados de acordo com as
diretrizes da consulta, para fornecer o
resultado.
Processamento de Consultas
Consulta SQL
É adequada para uso humano;
Não adequada ao processamento pelo SGBD:
Não descreve uma seqüência de passos
(procedimento) a ser seguida;
Não descreve uma estratégia eficiente para a
implementação de cada passo no que tange o
acesso em nível físico (arquivos do BD).
Cabe ao SGBD deve se preocupar com este
processamento módulo Processador de Consultas.
Módulo Processador de Consultas
Objetivo: Otimização do processamento de uma
consulta
Tradução, transformação e geração de uma estratégia
(plano) de execução;
Estratégia de acesso:
Considera algoritmos predefinidos para implementação de
passos do processamento e estimativas sobre os dados.
O esforço é valido, pois quase sempre
Tx << Ty
Tx = Tempo para definir e executar uma estratégia
otimizada de processamento;
Ty = Tempo para executar uma estratégia nãootimizada de processamento.
Passos no Processamento de Consultas
Consulta
Analisador
sintático
e tradutor
Expressão
algébrica
relacional
Otimizador
Saída da
Avaliador
consulta
Dados
Plano de
execução
Metadados
Passos no Processamento de Consultas
Consulta
Analisador
sintático
e tradutor
Expressão
algébrica
relacional
Otimizador
• Análise léxica
- cláusulasSaída
SQL
da e nomes válidos.
Avaliador
consulta
• Análise sintática
- validação da gramática.
• Análise semântica
- nomes usados de acordo com a estrutura
do esquema.
Dados
• Conversão para uma árvore algébrica
da consulta
Plano de
execução
Metadados
Passos no Processamento de Consultas
Consulta
Analisador
sintático
e tradutor
Expressão
algébrica
relacional
Otimizador
• Definição de uma árvore de
consulta equivalente
Saída da
Avaliador
- chega ao mesmo
resultado
consulta
- processa de forma mais
eficiente
• Fase chamada de
Otimização Algébrica
Dados
Plano de
execução
Metadados
Passos no Processamento de Consultas
Análise de alternativas de definição de
Analisador
estratégias de acesso
sintático
- escolha de Consulta
algoritmos para
e tradutor
implementação de operações
- existência de índices
- estimativas sobre os dados
(tamanho de tabelas, seletividade, ...)
Saída da
Avaliador
consulta
Dados
Expressão
algébrica
relacional
Otimizador
Plano de
execução
Metadados
Passos no Processamento de Consultas
Consulta
Analisador
sintático
e tradutor
FOCO:
OTIMIZADOR DE
CONSULTA
Saída da
Expressão
algébrica
relacional
Otimizador
Avaliador
consulta
Dados
Plano de
execução
Metadados
Exemplo Introdutório
Suponha a consulta:
select saldo
from conta
where saldo < 2500;
Esta pode ser traduzida nas duas expressões algébricas
relacionais diferentes:
σ
saldo < 2500 (π saldo (conta))
π saldo (σ saldo < 2500(conta))
Exemplo Introdutório
Além desta variação, é possível executar cada operação
algébrica relacional usando um entre diversos
algoritmos diferentes. Por exemplo:
Para executar a seleção, podemos procurar em todas
as tuplas de conta a fim de encontrar as tuplas com
saldo menor 2.500.
Se um índice árvore-B+ estiver disponível no atributo
saldo, podemos usar o índice em vez de localizar as
tuplas.
É necessário prover as expressões algébricas de
anotações que permitam especificar como serão
avaliadas.
Exemplo Introdutório
Uma operação algébrica relacional anotada com
instruções sobre como ser avaliada é chamada
de avaliação primitiva.
Vária avaliações primitivas podem ser
agrupadas em pipeline, e executadas em
paralelo.
Uma seqüência de operações primitivas é um
plano de execução de consulta ou plano de
avaliação de consulta.
Exemplo Introdutório
πsaldo
σsaldo < 2500 (use índice 1)
conta
Uma vez escolhido o plano de consulta, a consulta é
avaliada com aquele plano e o resultado da consulta é
produzido
Otimização de Consultas
Existem 2 técnicas básicas para otimização de
consultas:
As baseadas em heurísticas para a ordenação de
acesso ao banco de dados, que participarão da
estratégia de acesso;
e as que estimam sistematicamente o custo de
estratégias de execução diferentes e escolhem o
plano de execução com o menor custo estimado.
Catálogo de Informações para Estimativa de
Custo
nr: é o número de tuplas na relação r;
br: é o número de blocos que contêm tuplas da relação r;
sr: é o tamanho em bytes de uma tupla da relação r;
fr: é o fator de bloco da relação r, ou seja, o número de
tuplas da relação r que cabe em um bloco;
V(A,r): é o número de valores distintos que aparecem na
relação r para o atributo A. Esse valor é igual ao
tamanho (em número de tuplas) de πA(r). Se A é uma
chave para a relação r, V(A,r) é nr.
Catálogo de Informações para Estimativa de
Custo
SC(A,r): é a cardinalidade de seleção (seletividade) do atributo A da
relação r.
Dados uma relação r e um atributo A da relação, SC(A,r) é o
número médio de registros que satisfazem uma condição de
igualdade no atributo A, dado que pelo menos um registro
satisfaz a condição de igualdade.
Exemplo:
SC(A,r) = 1 se A é um atributo-chave de r;
Para um atributo que não é chave, estimamos que os
valores distintos de V(A,r) são distribuídos uniformemente
entre as tuplas, produzindo SC(A,r) = (nr / V(A,r))
Catálogo de Informações para Estimativa de
Custo
As duas últimas estatísticas podem ser
estendidas de forma a valer para um conjunto
de atributos, ao invés de valer para apenas um
atributo.
Se as tuplas da relação r estiverem
armazenadas fisicamente juntas em um arquivo,
a seguinte equação é válida:
Br = [nr, fr]
Catálogo de Informações para Estimativa de
Custo
Informações sobre índices:
fi: é o fan-out (número de ponteiros) médio dos nós
internos do índice i para índices estruturados em
árvore, como árvores B+;
HTi: é o número de níveis no índice i, ou seja, a altura
do índice i.
LBi: é o número de blocos de índice de nível mais
baixo no índice i, ou seja, o número de blocos no
nível de folha do índice (o número de blocos que
contém os registros folha de um índice).
Catálogo de Informações para Estimativa de
Custo
As variáveis estatísticas são usadas para estimar o tamanho
do resultado e o custo para várias operações e algoritmos.
A estimativa de custo do algoritmo A é EA.
Para manter as estatísticas precisas, toda vez que uma
relação for modificada tem-se que atualizar as estatísticas.
Contudo, a maioria do sistema não atualiza as estatísticas em
todas as modificações. Atualiza-as periodicamente.
Quanto mais informações forem utilizadas para estimar o
custo da consulta e quanto mais precisas forem essas
informações, melhores serão as estimativas de custo.
Medidas do Custo de uma Consulta
O custo de uma consulta pode ser estimado de diversas formas:
Por acessos a disco;
Por tempo de uso da CPU;
Pelo tempo de comunicação nos BD paralelos
e/ou distribuídos;
O tempo de execução de um plano poderia ser usado para
estimar o custo da consulta, contudo em grandes sistemas de
BD, utiliza-se o número de acessos a disco, porque estes
estabelecem o tempo crítico de execução do plano (já que são
lentos quando comparados às operações realizadas em
memória).
Medidas do Custo de uma Consulta
Para simplificar nossos cálculos assumiremos que todas as
transferências de blocos (do disco para memória) têm o
mesmo custo. Desconsideraremos o tempo de latência e o
tempo de busca. Também desconsideramos o custo de
escrever o resultado final de uma operação de volta para o
disco.
Os custos dos algoritmos dependem significativamente do
tamanho do buffer na memória principal. No melhor caso,
todos os dados podem ser lidos para o buffer e o disco não
precisa ser acessado novamente. No pior caso, supomos
que o buffer pode manter apenas alguns blocos de dados
– aproximadamente um bloco por relação. Geralmente
faremos a suposição do pior caso.
Operação de Seleção
É a varredura de arquivos: o operador de mais baixo
nível para se ter acesso aos dados.
São algoritmos de procura que localizam e recuperam
os registros que estão de acordo com uma condição de
seleção.
Tem-se vários algoritmos diferentes, que variam de
acordo com a complexidade da seleção e o uso ou não
de índices.
Operação de Seleção
Exemplo de algoritmos usados na implementação do
operador select:
Busca Linear (ou força bruta);
Busca Binária;
Utilização de índice primário (atributo chave);
Utilização de índice primário para recuperar múltiplos
registros (atributo chave);
Utilização de um índice cluster para recuperar
múltiplos registros (atributo não chave);
Utilização de um índice secundário (Árvore B+) em
uma comparação de igualdade;
Busca para seleções complexas
Operação de Seleção
Busca para seleções complexas:
Se uma condição de uma instrução select é uma
condição conjuntiva – ou seja, é formada por diversas
condições simples conectadas pelo conectivo lógico
AND, o SGBD pode usar os seguintes métodos:
Seleção conjuntiva utilizando um índice individual;
Seleção conjuntiva utilizando um índice composto;
Seleção conjuntiva por meio da interseção de registros.
Operação de Seleção
Busca para seleções complexas:
Se uma condição de uma instrução select é uma
condição disjuntiva – ou seja, é formada por diversas
condições simples conectadas pelo conectivo lógico
OR, a otimização é mais simples.
Pouca otimização pode ser feita, pois os registros
que satisfazem a condição disjuntiva são a união dos
registros que satisfazem as condições individuais.
Operação de Seleção
Veremos dois deles (os básicos):
Aquele que envolve uma Busca Linear;
Aquele que envolve uma Busca Binária.
Considere uma operação de seleção em uma
relação cujas tuplas são armazenadas juntas
em um único arquivo.
Seleção por Busca Linear – A1
Em uma busca linear, cada bloco de arquivo é varrido e
todos os registros são testados para verificar se
satisfazem a condição de seleção.
Como todos os blocos precisam ser lidos, EA1 = br.
No caso da seleção ser aplicada em um atributo-chave,
podemos supor que a metade dos blocos é varrida antes
de o registro ser encontrado, ponto no qual a varredura
termina. A estimativa então será EA1 = (br/2).
Seleção por Busca Binária – A2
Se o arquivo é ordenado em um atributo e a condição de seleção é
uma comparação de igualdade neste atributo, podemos usar uma
busca binária para localizar os registros que satisfazem a seleção.
Neste caso, a estimativa é:
EA2 = [log2(br)] + [SC(A,r)/fr] -1
O primeiro termo [log2(br)] contabiliza o custo para localizar a
primeira tupla por meio da busca binária nos blocos;
O número total de registros que satisfarão a seleção é SC(A,r), e
esses registros ocuparão [SC(A,r)/fr] blocos, dos quais um já
havia sido recuperado (por isso o -1).
Se a condição de igualdade estiver em um atributo-chave, então
SC(A,r) = 1, e a estimativa se reduz a EA2 = [log(br)].
Cálculo do Custo da Busca Binária
Acesso aos blocos:
Primeiro acesso (ao bloco central) não encontro o registro
procurado;
Segundo acesso (ao bloco central do lado esquerdo ou direito)
....
Até o pior caso (nono acesso), o registro é encontrando na
última divisão disponível (ou não é encontrado).
Para 500 blocos:
500 250 125 62,5 31,25 15,62 7,8 3,9 1,9 (nove divisões)
Cálculo: log2(500) = 9 29 = 516 (=~ 500)
Exemplo de Seleção por Busca Binária
Suponha as seguintes informações estatísticas para uma relação
conta:
fconta = 20 (ou seja, 20 tuplas de conta cabem em um único
bloco);
V(nome_agência, conta) = 50 (ou seja, existem 50 agências
com nomes diferentes);
V(saldo, conta) = 500 (ou seja, existe 500 valores diferentes de
saldos nesta relação);
nconta = 10.000 (ou seja, a relação conta possui 10.000 tuplas).
Considere a consulta:
σ nome_agência = Perryridge (conta)
Exemplo de Seleção por Busca Binária
Como a relação tem 10.000 tuplas, e cada bloco mantém 20 tuplas, o
número de blocos é bconta = 500 (10.000/20);
Uma varredura de arquivo simples faria 500 acessos a blocos, supondo
que o atributo da condição não fosse atributo-chave. Senão, seriam em
média 250 acessos;
Suponha que conta esteja ordenado por nome_agência.
Como V(nome_agência, conta) = 50, esperamos que 10.000/50=200
tuplas da relação conta pertençam à agência Perryridge;
Essas tuplas caberiam em 200/20 = 10 blocos;
Uma busca binária para encontra o primeiro registro [log2(500)] = 9;
Assim o custo total seria: 9 + 10 – 1 = 18 acessos a bloco.
Operação de Seleção
A otimização de consulta para uma operação SELECT é
necessária principalmente em condições de seleção
conjuntiva, sempre que mais de um dos atributos
envolvidos nas condições possuírem um caminho de
acesso.
O otimizador deve escolher o caminho de acesso que
recupera o menor número de registros (gera blocos de
respostas menores), de maneira mais eficiente.
As seleções que separam o menor número de tuplas
devem ser realizadas primeiro.
Na escolha entre múltiplas opções o otimizador
considera também a seletividade de cada condição.
Classificação
A ordenação é bastante importante, uma vez que o
algoritmo é utilizado:
Na implementação do order by.
Como um componente-chave nos algoritmos de sortmerge usado no join, union e intersection e em
algoritmos de eliminação de duplicatas para a
operação project.
A ordenação pode ser evitada se um índice apropriado
existir de forma a permitir o acesso ordenado aos
registros.
Classificação
Formas de ordenação:
Lógica: construção de um índice na chave de
classificação, o qual será usado para ler a
relação na ordem de classificação.
A leitura de tuplas na ordem de classificação pode
conduzir a um acesso de disco para cada tupla.
Física: as tuplas são gravadas de forma
ordenada no disco.
Classificação
O problema de classificação pode ser tratado sob duas
condições:
Quando
a relação cabe completamente na memória
principal:
Técnicas padrões de classificação (quicksort entre outras)
podem ser usadas.
Quando
a relação é maior que a memória principal classificação externa:
Algoritmo comum: sort-merge externo
Para entendê-lo, considere M o número de frames de páginas
no buffer da memória principal ( o número de blocos de disco
cujos conteúdos podem ser colocados no buffer da memória
principal).
Ordenação Externa
A ordenação externa é adequada para manipular
arquivos de registros grandes, que são armazenados
em disco e que não cabem inteiramente na memória
principal.
A ordenação nesse algoritmo é feita por partes –
estratégia merge-sort.
Fases:
Fase de ordenação;
Fase de fusão.
Inicialização:
i  1;
j  b; (tamanho do arquivo em blocos)
k  n0; (tamanho do buffer em blocos)
m   (j/k)  (maior inteiro)
Fase de ordenação
Se no buffer cabem 3 blocos,
e o arquivo possui 11 blocos,
será preciso 4 iterações da
fase de ordenação. As 3
primeiras ordenarão 9 blocos
e a última ordenará 2 blocos.
Enquanto (i <= m) faça
{
leia os próximos k blocos do arquivo para o buffer ou se houver
menos do que k blocos restantes, leia todos os blocos restantes;
ordene os registros no buffer e grave-os como um sub-arquivo
temporário (utilize um algoritmo de ordenação interna, como o
bubble ou quicksort, por exemplo);
i  i + 1;
}
Fase de fusão: fundir os subarquivos até que reste apenas 1
Inicialização
Temos 4 subarquivos ordenados (m = 4 e k = 3).
i  1;
p   logk-1m ; (p é o número de passagens da fase de fusão)
j  m;
p=2
enquanto (i <= p) faça
{
n  1;
q = 2 e depois 1
q   (j/(k-1)) ; (número de subarquivos a gravar nesta passagem)
enquanto (n <= q) faça
{
ler os próximos k-1 subarquivos ou os subarquivos
restantes (da passagem anterior), um bloco de cada arquivo por vez;
fundir e gravar no novo subarquivo um bloco por vez
n  n + 1;
}
j  q;
i  i + 1;
}
Exemplo no Navathe
Se o número de blocos do arquivo = 1024
Se o tamanho do buffer = 5 blocos
Na fase de ordenação serão criados 205 subarquivos
204 com 5 blocos e 1 com 4 blocos
Na fase de fusão, em cada uma das 4 passagens, serão gravados,
respectivamente:
52 subarquivos
13 subarquivos
04 arquivos
01 arquivo
Número de subarquivos / tamanho do buffer -1 bloco
Por que -1?
Porque um bloco de buffer fica reservado
para armazenar um bloco resultado da fusão.
Sort-merge Externo (Korth)
1.
Várias classificações temporárias são executadas:
i = 0;
repeat
leia M blocos da relação, ou o resto da relação,
aquilo que for menor;
ordene a parte da relação que está na memória;
escreva os dados ordenados no arquivo temporário
Ri;
i = i + 1;
until o fim da relação
Sort-merge Externo
2.
Faz-se o merge nos arquivos temporários. Suponha, por enquanto, que
o número total de temporários, N, seja menor do que M, de forma que se
consiga alocar um frame de página para um bloco de cada arquivo
temporário e há espaço para manter uma página de resultado.
leia um bloco de cada um dos N arquivos Ri, para uma página de buffer
na memória;
repeat
escolha a primeira tupla (na ordem de classificação) entre todas as
páginas do buffer;
escreva a tupla no resultado e apague-a da página de buffer;
if a página de buffer de qualquer temporário Ri está vazia and not fim
de arquivo (Ri) then leia o próximo bloco de Ri na página de buffer;
until todas as páginas de buffer estarem vazias;
Considerações
Geralmente, se a relação é muito maior que a memória, pode haver
M ou mais temporários gerados na primeira fase, e não será
possível alocar um frame de página para cada temporário durante a
fase de merge.
Neste caso, faz-se a operação de merge em múltiplos passos.
Como há memória suficiente para M-1 páginas de buffer de entrada,
cada merge terá M-1 temporários como entrada.
Funcionamento próximo slide.
Exemplo: Suponha agora que apenas um tupla caiba em um bloco
(f = 1), e suponha que a memória mantém três frames de página no
máximo. Durante os estágios de merge, dois frames de página são
usados para entrada e um para o resultado.
Funcionamento
Faz-se o merge sobre os primeiros M-1 temporários (conforme descrito
anteriormente) para obter um único temporário para o próximo passo;
Faz-se o merge dos próximos M-1 temporários de forma semelhante, e
assim por diante, até que todos os temporários iniciais tenham sido
processados;
Nesse ponto, o número de temporários foi reduzido a um fator de M–1;
Se esse número reduzido de temporários ainda é maior ou igual a M, outro
passo é dado, usando os temporários criados pelo passo anterior;
Esses passos são repetidos tantas vezes quantas forem necessárias, até
que o número de temporários seja menor que M;
Então, um passo final gera o resultado classificado.
Exemplo
g
a
d
c
b
24
19
31
33
14
e
16
r
16
d
m
p
d
a
19
a
19
d
31
b
14
g
24
c
33
d
31
e
16
g
24
21
a
14
3
d
7
16
d
21
m
3
b
14
c
33
e
16
d
21
3
m
2
r
7
a
14
Relação
inicial
a
14
p
2
d
7
r
16
p
2
Temporários
Criar
temporários
Temporários
Passo 1:
de merge
a
14
a
19
b
14
c
33
d
7
d
21
d
31
e
16
g
24
m
3
p
2
r
16
Resultado
classificado
Passo 2:
de merge
Número de Acessos a Disco
Fase de ordenação:
2 * b, onde b é o número de blocos do arquivo que está sendo
ordenado
Cada bloco b será acessado duas vezes, uma vez para leitura e
outra vez para escrita
Fase de fusão:
2 * (b * log Dm nr), onde Dm é o número de subarquivos fundidos
em cada fusão e nr é número de subarquivos.
O 2 se dá por conta da leitura e escrita de cada bloco
O termo interno ao parênteses conta quantas vezes cada bloco
será analisado (lido e escrito)
Operação de Junção
equi_join: designação para uma junção da forma r |X|r.A=s.B s, em que A e B
são atributos ou conjuntos de atributos das relações r e s, respectivamente.
O exemplo usado será:
depositante |X| cliente
Suponha as seguintes informações de catálogo:
ncliente = 10.000
fcliente = 25, o que implica bcliente = 10.000/25 = 400
ndepositante = 5.000
fdepositante = 50, o que implica bdepositante = 5.000/50 = 100
V(nome_cliente, depositante) = 2.500, o que implica que, em média,
cada cliente tem duas contas
Suponha ainda que nome-cliente em depositante seja uma chave
estrangeira vinda de cliente
Estimativa do Tamanho das Junções
O produto cartesiano r X s contém nr * ns tuplas.
Cada tupla deste produto cartesiano ocupa sr + ss bytes.
Assim podemos calcular o tamanho do produto
cartesiano.
Para junção natural ... Sejam r(R) e s(S) duas relações:
Se R ∩ S = ∅, então r |X| s é igual a r X s;
Se R ∩ S é uma chave para R, então sabemos que
uma tupla de s irá juntar-se com no máximo uma
tupla de r. Assim, o número de tuplas na junção não
é maior que o número de tuplas de s.
Se R ∩ S é uma chave estrangeira para S – vinda de
R – , então o número de tuplas em r |X| s é
exatamente igual ao número de tuplas em s.
Estimativa do Tamanho das Junções
No exemplo: depositante |x| cliente,
nome_cliente em depositante é uma chave
estrangeira vinda de cliente.
O tamanho do resultado é exatamente
ndepositante, que é 5.000;
Com calcular o tamanho da junção quando R ∩
S não é uma chave para R ou para S?
Estimativa do Tamanho das Junções
Suponha que cada valor aparece com probabilidade igual.
Considere uma tupla t de r e suponha R ∩ S = {A}.
Estima-se que a tupla t produz
ns / V(A,s)
tuplas em r |X| s, uma vez que esse é o número médio de tuplas em s
com um determinado valor para os atributos A.
Considerando todas as tuplas em r, estima-se que há
nr * ns / V(A, s)
tuplas em r |X| s.
Estimativa do Tamanho das Junções
Observe que se invertermos os papéis de r e s, as estimativas
resultariam em valores diferentes se V(A,r) ≠ V(A,s).
Se isso acontece, há a probabilidade de haver tuplas pendentes
que não participam da junção . A estimativa mais baixa será,
provavelmente, a mais precisa.
Técnicas mais sofisticadas para a estimativa do tamanho da junção
devem ser usadas se a hipótese de distribuição uniforme não puder
ser considerada.
Estimativa do Tamanho das Junções
Calculando a estimativa do tamanho para depositante
|X| clientes, sem utilizar informações sobre chaves
entrangeiras.
Como V(nome_cliente, depositante) = 2.500 e
V(nome_cliente, cliente) = 10.000, as duas estimativas
que obtemos são:
(10.000 * 5.000) / 2.500 = 20.000
(5.000 * 10.000)/10.000 = 5.000
Junção de Laço Aninhado
for each tupla tr in r do
begin
for each tupla ts in s do
begin
teste o par (tr, ts) para ver se
satisfazem a condição de junção;
se satisfizerem, adicione tr.ts ao
resultado
end
end
r: relação externa
s: relação interna
tr.ts : tupla obtida concatenando os valores dos atributos das tuplas tr e
ts
Junção de Laço Aninhado
Este algoritmo não requer índices e pode ser usado seja qual for a
condição de junção.
É um algoritmo caro já que examina todos os pares de tuplas nas duas
relações. O número de pares de tuplas a ser considerado é nr * ns (para
cada registro r tem-se que executar uma varredura completa em s).
No pior caso o buffer pode manter apenas um bloco de cada relação, e um
total de nr * bs + br acessos à blocos serão necessários (ou seja, os blocos
da relação r (br) são lidos uma vez por ocasião do laço mais externo e, os
blocos da relação s (bs) são lidos para cada vez que uma tupla de r precisa
ser comparada com todas as tuplas de s por ocasião do laço mais interno)
No melhor caso, há espaço suficiente para que ambas as relações caibam
na memória, assim cada bloco terá de ser lido somente uma vez,
conseqüentemente, apenas br + bs acessos à blocos serão necessários.
Note que, se a relação menor couber completamente na memória, é melhor
usar essa relação como a mais interna.
Exemplo
Considere a junção natural de depositante e cliente.
Suponha que não existem índices para estas relações.
Suponha que depositante é a relação mais externa e
cliente é a relação mais interna.
5.000 * 10.000 tuplas serão examinadas.
Pior caso: 5.000 * 400 + 100 = 2.000.100 acessos à
disco.
Melhor caso: 400 + 100 = 500 acessos à disco.
Trocando as relações dos laços internos e externos:
10.000 * 100 + 400: 1.000.400 acessos à disco.
Merge-junção (Korth)
Sejam r(R) e s(S) relações cuja junção natural
será calculada, e seja R ∩ S a notação para
seus atributos em comum.
Suponha que ambas as relações estejam
classificadas nos atributos R ∩ S.
A junção destas relações pode ser feita por
meio de um merge.
pr := endereço da primeira tupla de r;
ps := endereço da primeira tupla de s;
while (ps <> nulo and pr <> nulo) do
begin
ts := tupla para qual ps aponta;
Ss := {ts};
configure ps para apontar para a próxima tupla de s;
acabou := false;
while (not acabou and ps <> nulo) do
begin
ts’ := tupla para qual ps aponta;
if (ts’[AtribJunção] = ts[AtribJunção])
then begin
Ss = Ss ∪ {ts’};
configure ps para apontar para
a próxima tupla de s;
end
else acabou := verdadeiro;
end;
// permanece varrendo s enquanto as tuplas contiverem valores iguais para o
atributo de junção, e as coloca em uma relação auxiliar.
tr := tupla para a qual pr aponta;
while ( pr <> nulo and tr[AtribJunção] < ts[AtribJunção]) do
begin
configure pr para apontar para a próxima tupla
de r;
tr := tupla para qual pr aponta;
end
// percorre r enquanto não encontrar uma tupla com um valor no atributo de
junção igual ou maior ao valor no atributo de junção das tuplas de s que estão
na relação auxiliar
while (pr <> nulo and tr[AtribJunção] = ts[AtribJunção]) do
begin
for each rs in Ss do
begin
adicione ts.tr ao resultado;
end
configure pr para apontar para a próxima tupla
de r;
tr := tupla para a qual pr aponta;
end;
// encontrando a tupla de r que deve ser juntar às tuplas de Ss, realiza a
concatenação das tuplas, percorrendo r para ver se existem outras a serem
concatenadas.
End;
Merge-junção
Suponha depositante |x| cliente. Com o atributo de
junção sendo o nome do cliente. As relações já estão
ordenadas neste atributo.
O custo da junção é 400 + 100 = 500 acessos à disco.
Caso a exigência de S caber em memória principal não
puder ser atendida, um algoritmo de junção à parte deve
ser executado para junção tr à Ss.
Caso as relações não estejam ordenadas mas possuam
índices, o merge-junção pode ser executado usando os
índices.
Junção Sort-merge (Navathe)
Se os registros de R e S estiverem classificados (ordenados)
fisicamente pelos atributos de junção A e B, respectivamente,
poderemos implementar a junção da maneira mais eficiente
possível.
Ambos os arquivos são varridos simultaneamente na ordem dos
atributos de junção, fazendo a correspondência dos registros que
possuem os mesmos valores para A e B.
Se os arquivos não estiverem classificados, eles deverão ser
classificados primeiro por meio de uma ordenação externa.
Pares de blocos de arquivos são ordenadamente copiados para
buffers de memória, e os registros de cada arquivos são varridos
apenas uma vez (a menos que A e B não sejam atributos chaves e,
nesse caso, o método precisa ser modificado).
Índices proporcionam a capacidade de acessar (varrer) os registros
na ordem dos atributos de junção, mas os registros de fato estão
fisicamente espalhados pelos blocos do arquivo.
Junção Sort-merge
A seguir, um esboço do algoritmo para Junção,
Projeção, União, Interseção e Diferença por
meio de sort-merge, quando R possui n tuplas e
S possui m tuplas.
T  R |X| A=B S
Ordenar as n tuplas de R baseando-se no atributo A;
Ordenar as m tuplas de S baseando-se no atributo B;
Inicializar i  1 , j  1;
Enquanto (i <= n) e (j <= m) faça
{
se Ri[A] > Sj[B]
se o valor do primeiro A é maior que o valor
então j j + 1
do primeiro B, avance em B;
senão se Ri[A] < Sj[B]
se o valor do primeiro A é menor que o valor
então i  i + 1
do primeiro B, avance em B;
senão
{
bloco do próximo slide
}
i i + 1; j  j + 1;
}
{
(* Ri[A] = Sj[B], portanto realizamos o output de uma tupla: resultado
da junção*)
output a tupla combinada <Ri[A], Sj[B]> em T;
(* output outras tuplas correspondentes a Ri se houver*)
l  j + 1;
enquanto (l<=m) and (Ri[A] = Sl[B]) faça
output a tupla combinada <Ri[A], Sl[B]> em T
ll+1
(* output outras tuplas correspondentes a S(j), se houver *)
k  i + 1;
enquanto (k<=n) and (Rk[A] = Sj[B]) faça
output a tupla combinada <Rk[A], Sj[B]> em T
k k + 1;
}
T π <lista de atributos> (R)
Criar uma tupla t[<lista de atributos>] em T’ para cada tupla t de R;
(*T’ contém o resultado da projeção ANTES da eliminação de duplicatas*)
Se <lista de atributos> incluir uma chave de R
então T  T’;
Senão
{
ordenar as tuplas de T’
inicializar i  1, j 2;
enquanto i <= n faça
{
output a tupla T’[i] em T;
enquanto T’[i] = T’[j] and j<= n faça
j j + 1; (* eliminar duplicatas)
i  j; j  j + 1;
}
}
TR∪S
Ordenar as tuplas de R e S utilizando os mesmos e únicos atributos de ordenação;
Inicializar i  1; j  1;
Enquanto (i <= n) e (j <= m) faça
{
se R(i) > S(j) então
{
output S(j) em T;
j  j + 1;
}
se R(i) < S(j) então
{
output R(i) em T;
i  i + 1;
}
else
j  j + 1; (*R(i)=S(j), portanto, pular uma das tuplas duplicatas*)
}
TR∩S
Ordenar as tuplas de R e S utilizando os mesmos e únicos atributos de ordenação;
Inicializar i  1; j  1;
Enquanto (i <= n) e (j <= m) faça
{
se R(i) > S(j) então
{
j  j + 1;
}
else se R(i) < S(j) então
{
i  i + 1;
}
else
{
output R(i) em T; (*R(i)=S(j), portanto, fazemos o output da tupla*)
i  i + 1; j  j + 1;
}
}
TR-S
Ordenar as tuplas de R e S utilizando os mesmos e únicos atributos de ordenação;
Inicializar i  1; j  1;
Enquanto (i <= n) e (j <= m) faça
{
se R(i) > S(j) então
{
j  j + 1;
}
else se R(i) < S(j) então
{
output R(i) em T; (*R(i) não tem S(j) correspondente)
i  i + 1;
}
else
i  i + 1; j  + 1
}
Merge-junção - Considerações
Em relação ao algoritmo apresentado por (Korth): O algoritmo exige
que a relação auxiliar caiba na memória principal. Modificações no
algoritmo devem ser feitas caso essa exigência não possa ser
atendida.
Dado que as relações estão na ordem de classificação, as tuplas
com o mesmo valor nos atributos de junção estão em ordem
consecutiva. Assim, cada tupla na ordem de classificação precisa
ser lida somente uma vez, e, como resultado, cada bloco também é
lido somente uma vez.
Em relação ao algoritmo do Navathe, tem-se que assumir que
conjuntos de tuplas com o mesmo valor no atributo de junção
precisam estar carregadas na memória ao mesmo tempo;
Então, para ambos, o número de acessos à disco é igual à soma do
número de blocos em ambos as relações, br + bs.
Implementação do Outer Join
A junção externa pode ser obtida por meio da
modificação dos algoritmos de junção, como a
junção de laços aninhados, sort-merge ou de
junção hash;
Ou, de forma alternativa e simplificada, por meio
da execução de uma combinação de
operadores da álgebra relacional.
Implementação do Outer Join
Por exemplo, considere a consulta:
select unome, pnome, dnome
from empregado left outer join departamento on
dno=dnumero;
Essa operação de junção externa é equivalente à
seguinte seqüência de operaçoes da álgebra relacional:
Implementação do Outer Join
Calcule a junção interna entre as tabelas.
Temp1  πunome, pnome, dnome (empregado |X| departamento)
Encontre as tuplas de empregado que não aparecem no
resultado da junção.
Temp2  πunome, pnome (empregado) - πunome, pnome (Temp1)
Implementação do Outer Join
Complete cada tupla da relação Temp2 com valor null
para o campo dnome.
Temp2  Temp2 X ‘NULL’
Aplique a operação union em Temp1 e Temp2 para
produzir o resultado do left outer join.
Resultado  Temp1 υ Temp2
O custo dessa junção externa é a soma dos custos da
junção interna, das projeções e da união realizadas.
Junções Complexas
Junção com condição conjuntiva:
r |X| θ ∧ θ ∧ ... θn s
1
2
As junções nas condições individuais podem ser
resolvidas, por exemplo, pelo algoritmo de junção por laços
aninhados:
r |X|θ s, r |X|θ 2 s, r |X| θ n s e assim por diante.
1
A junção global por ser realizada calculando, primeiro o
resultado de uma dessas junções mais simples e depois
testando (a esse resultado) as tuplas produzidas pelas
outras junções.
Junções Complexas
Junção com condição disjuntiva:
r |X| θ 1 ∨ θ 2 ∨ ... θ n s
Neste caso, a junção pode ser calculada como a
união dos registros nas junções individuais.
Junções Complexas
Suponha r1 r2 ...
rn em que as junções estão expressas
sem ordem. Com n = 3, há 12 ordens de junção diferentes:
r1
r2
r3
r1
r2
r3
(r2
(r1
(r1
(r3
(r3
(r2
(r2
(r1
(r1
(r3
(r3
(r2
r3)
r3)
r2)
r2)
r1)
r1)
r3)
r3)
r2)
r2)
r1)
r1)
r1
r2
r3
r1
r2
r3
Junções Complexas
Em geral, com n relações, há (2(n-1))! / (n-1)!
Ordens de junção diferentes. Exemplos: Com n
= 5 o n° é 1680 e com n = 7, o n° é 665.280.
Felizmente, não é necessário gerar todas as
expressões equivalentes a uma determinada
expressão.
Uma desvantagem da otimização baseada no
custo é o custo da própria otimização.
Junções Complexas
Duas árvores de consulta (junção) profundas
à esquerda
Junções Complexas
O otimizador escolherá a árvore que possuir o
menor custo estimado.
Com árvores profundas a esquerda, o filho à
direita é considerado ser a relação interna, para
o caso da execução de laços aninhados.
A idéia-chave sob o ponto de vista do otimizador
em relação à ordem das junções é encontrar
uma ordem que irá reduzir o tamanho dos
resultados intermediários.
Junções Complexas
Considere uma junção envolvendo três relações:
empréstimo |X| depositante |X| cliente
Neste caso, além da escolha da estratégia para o processamento
da junção, tem-se ainda que escolher qual junção calcular primeiro.
Vejamos algumas estratégias:
Estratégia 1: calcule a junção depositante |X| cliente usando
qualquer técnicas. Usando o resultado intermediário, calcule:
empréstimo |X| (depositante |X| cliente);
Estratégia 2: faça como na Estratégia 1, mas calcule primeiro
empréstimo |X| depositante, e então faça a junção do resultado
com cliente.
Outra ordem de junções pode ser feita.
Junções Complexas
Estratégia
3: Em vez de executar duas junções,
execute o par de junções, da seguinte forma:
Construa dois índices:
Um para o número_empréstimo em empréstimo;
Um para o nome_cliente em cliente.
Considere cada tupla t em depositante. Para cada t, procure
as tuplas correspondentes em cliente e as tuplas
correspondentes em empréstimo.
Assim, cada tupla de depositante é examinada exatamente
uma vez.
O custo relativo desse procedimento depende da
forma como as relações estão armazenadas, da
distribuição de valores dentro das colunas e da
presença de índices.
Eliminação de Duplicidade
Pode-se implementar a eliminação de duplicidade usando a
classificação.
As tuplas idênticas aparecerão adjacentes umas às outras após a
classificação, e todas, exceto uma cópia, podem ser removidas.
No sort-merge, as duplicatas encontradas enquanto um temporário
está sendo criado podem ser removidas antes que ele seja escrito
no disco, reduzindo, assim, o número de transferências de blocos.
Assim, pode-se dizer que o custo de eliminar as duplicatas é o
custo de classificar uma relação.
Devido ao custo relativamente alto da eliminação de duplicidade, as
linguagens de consulta comerciais exigem um pedido explícito do
usuário para remover duplicatas; caso contrário, as duplicatas são
mantidas.
Operação de Projeção
Pode-se executar a projeção por meio da execução da
projeção em cada tupla, resultando uma relação que
poderia ter registros duplicados, e então, remover os
registros duplicados.
Se os atributos na lista de projeção incluem as chaves
da relação (primária e/ou candidatas), nenhuma
duplicata existirá.
O tamanho de um projeção da forma ΠA(r) é calculado
como V(A,r), uma vez que a projeção elimina as
duplicatas.
Transformações de Expressões
Relacionais
Uma consulta pode ser expressa de diversas
maneiras diferentes, com diferentes custos de
avaliação.
Equivalência de Expressões;
Regras de Equivalência;
Exemplos de Transformações;
Ordenamento de Junções.
Otimização Algébrica
Objetivo do passo de transformação
Entrada: Árvore da consulta inicial;
Saída: Árvore da consulta otimizada (pode
manter a mesma árvore).
Base:
Regras de equivalência algébrica
Devem ser conhecidas pelo otimizador para que
possam ser geradas transformações válidas.
Algoritmo de otimização algébrica
Indica a ordem de aplicação das regras e de outros
processamentos de otimização.
Equivalência de Expressões
Considerando as tabelas a seguir e suas
instâncias, encontre os nomes de todos os
clientes que possuem uma conta em qualquer
agência localizada no Brooklyn.
πnome_cliente ( σcidade_agência = “Brooklyn” (agência |X| (conta |X|
depositante)))
Para resolver esta expressão, seguindo a forma como
ela está escrita, é necessário criar uma relação
intermediária grande (a junção das três relações, como
posto no slide 86).
agência
nome_agência
cidade_agência
fundos
Downtown
Brooklyn
900000
Redwood
Palo Alto
210000
Perrydige
Horseneck
170000
Mianus
Horseneck
40000
Round Hill
Horseneck
8000000
Pownal
Bennington
30000
North Town
Rye
370000
Brighton
Brooklyn
710000
cliente
nome_cliente
rua_cliente
cidade_cliente
Jones
Main
Harrison
Smith
North
Rye
Hayes
Main
Harrison
Curry
North
Rye
Lindsay
Park
Pittfield
Turner
Putnam
Stamford
Williams
Nassau
Princeton
Adams
Spring
Pittsfield
Johnson
Alma
Palo Alto
Glenn
Sand Hill
Woodside
Brooks
Senator
Brooklyn
Green
Walnut
Stamford
nome_cliente
número_conta
Johnson
A-101
nome_agência
número_conta
saldo
Smith
A-215
Downtown
A-101
500
Hayes
A-102
Mianus
A-215
700
Turner
A-305
Perryridge
A-102
400
Johnson
A-201
Round Hill
A-305
350
Jones
A-217
Bringhton
A-201
900
Lindsay
A-222
Redwood
A-222
700
Bringhton
A-217
750
depositante
conta
Junção (conta |X| depositante)
nome_agência
número_conta
saldo
nome_cliente
número_conta
Downtown
A-101
500
Johnson
A-101
Mianus
A-215
700
Smith
A-215
Perryridge
A-102
400
Hayes
A-102
Round Hill
A-305
350
Turner
A-305
Bringhton
A-201
900
Johson
A-201
Redwood
A-222
700
Lindsay
A-222
Bringhton
A-217
750
Jones
A-217
Junção (agência |X| (conta |X| depositante))
nome_agência
número_conta
saldo
nome_cliente
nome_agência
cidade_agência
fundos
Downtown
A-101
500
Johnson
Downtown
Brooklyn
900000
Mianus
A-215
700
Smith
Mianus
Horseneck
40000
Perryridge
A-102
400
Hayes
Perrydige
Horseneck
170000
Round Hill
A-305
350
Turner
Round Hill
Horseneck
8000000
Bringhton
A-201
900
Johson
Brighton
Brooklyn
710000
Redwood
A-222
700
Lindsay
Redwood
Palo Alto
210000
Bringhton
A-217
750
Jones
Brighton
Brooklyn
710000
Equivalência de Expressões
Entretanto, somente as tuplas que pertencem às
agências localizadas no “Brooklyn” são interessantes.
Reescrevendo a consulta, consegue-se eliminar a
necessidade de considerar as tuplas que não têm
cidade_agência = “Brooklyn”, reduzindo o tamanho do
resultado intermediário:
πnome_cliente (( σcidade_agência = “Brooklyn” (agência)) |X| (conta |X|
depositante))
Junção (conta |X| depositante)
nome_agência
número_conta
saldo
nome_cliente
número_conta
Downtown
A-101
500
Johnson
A-101
Mianus
A-215
700
Smith
A-215
Perryridge
A-102
400
Hayes
A-102
Round Hill
A-305
350
Turner
A-305
Bringhton
A-201
900
Johnson
A-201
Redwood
A-222
700
Lindsay
A-222
Bringhton
A-217
750
Jones
A-217
σcidade_agência = “Brooklyn” (agência)
nome_agência
cidade_agência
fundos
Downtown
Brooklyn
900000
Brighton
Brooklyn
710000
(σcidade_agência = “Brooklyn” (agência)) |X| (conta |X| depositante)
nome_agência
número_conta
saldo
nome_cliente
cidade_agência
fundos
Downtown
A-101
500
Johnson
Brooklyn
900000
Bringhton
A-201
900
Johnson
Brooklyn
710000
Bringhton
A-217
750
Jones
Brooklyn
710000
Equivalência de Expressões
πnome_cliente
πnome_cliente
σcidade_agência = “Brooklyn”
|X|
σcidade_agência = “Brooklyn”
|X|
|X|
agência
conta
agência
conta
|X|
depositante
depositante
(a) Árvore da expressão inicial
(b) Árvore da expressão transformada
Equivalência de Expressões
Dada uma expressão de álgebra relacional, é função do
otimizador de consulta propor um plano de avaliação da
consulta que gere o mesmo resultado da expressão
fornecida e que seja uma maneira menos onerosa de
gerar o resultado (ou que, pelo menos, não seja muito
mais cara que a maneira mais barata).
Para isso o otimizador precisa gerar planos alternativos
que produzam o mesmo resultado da expressão dada e
escolher o menos caro.
Regras de Equivalência Algébrica
Uma regra de equivalência diz que expressões de duas formas são
equivalentes se podemos transformar uma na outra preservando a
equivalência.
Preservar a equivalência significa que as relações geradas pelas
duas expressões têm o mesmo conjunto de atributos e contêm o
mesmo conjunto de tuplas, embora seus atributos possam estar
ordenados de forma diferente.
As regras de equivalência são usadas pelo otimizador para
transformar expressões em outras logicamente equivalentes.
Assuma que:
θ: denota predicados;
L: denotas listas de atributos;
E: denota expressões da álgebra relacional.
Regras de Equivalência Algébrica
1. Operações de seleção conjuntivas podem ser quebradas
em uma seqüência de seleções individuais (cascata de σ).
σθ1 ∧ θ2 (E) = σ θ1 (σ θ2 (E))
2. Operações de seleção são comutativas.
σ θ1 (σ θ2 (E)) = σ θ2 (σ θ1 (E))
3. Apenas as operações finais em uma seqüência de
operações de projeção são necessárias, as outras podem
ser omitidas (cascata de π).
πL1 (π L2 (...(π Ln(E))...)) = π L1(E)
Regras de Equivalência Algébrica
4. Seleções podem ser combinadas com produtos
cartesianos e junções teta.
σθ (E1 X E2) = E1 |X|θ E2
5. Operações de junção teta são comutativas.
E1 |X|θ E2 = E2 |X|θ E1
6. Operações de junção natural são associativas.
(E1 |X| E2) |X| E3 = E1 |X| (E2 |X| E3)
7. Comutatividade de π e |X| (ou X): similar à 6.
Regras de Equivalência Algébrica
8. Comutatividade de Operações de Conjunto
R∪S ≡ S∪R
e
R∩S ≡ S∩R
- A operação “” não é comutativa.
9. Associatividade de Operações Produtórias e de Conjunto
(“οX”)
(R “οX” S) “οX” T ≡ R “οX” (S “οX” T)
- Por “οX” entenda-se: X ou X θ ou
- A operação “” não é associativa.
ou ∪ ou ∩.
Regras de Equivalência Algébrica
9. Associatividade de Operações Produtórias e de Conjunto
(“οX”)
(R “οX” S) “οX” T ≡ R “οX” (S “οX” T)
Observação: Predicados de junção devem ser
devidamente ajustados na associatividade de operações
produtórias. Exemplo: Seja θ1 um predicado sobre
atributos de R e S, θ2 um predicado sobre atributos de S
e T, e θ3 um predicado sobre atributos de R e T. Então,
(R “X” θ1 S) “X” θ2 ∧ θ3 T ≡ R “X” θ1 ∧ θ3 (S “X” θ2 T)
Regras de Equivalência Algébrica
10. Comutatividade de Seleção e Operações de Conjunto
(“ο”)
σc (R “ο” S) ≡ (σc (R)) “ο” (σc (S))
- Por “ο” entenda-se: ∪ ou ∩ ou 
11. Comutatividade de Projeção e União
πlistaAtributos (R ∪ S) ≡ (πlistaAtributos (R)) ∪ (πlistaAtributos (S))
- As operações “” e “∩” não são comutativas.
Regras de Equivalência Algébrica
12. Fusão de Seleções e Operações Produtórias
(a)
σc (R X S) ≡ R X θ = σ
(b)
σc (R X S) ≡ R
(c)
R X θ = σc S ≡ R
c
c
S
c
S
S
ou
ou
Exemplos de Transformações
Exemplo 1:
πnome_cliente ( σcidade_agência = “Brooklyn” (agência |X| (conta |X| depositante)))
πnome_cliente (( σcidade_agência = “Brooklyn” (agência)) |X| (conta |X| depositante))
Exemplo 2:
πnome_cliente ( σcidade_agência = “Brooklyn” ∧ saldo > 1000 (agência |X| (conta |X| depositante)))
πnome_cliente ( σcidade_agência = “Brooklyn” ∧ saldo > 1000 ((agência |X| conta) |X| depositante))
πnome_cliente ( σcidade_agência = “Brooklyn” ∧ saldo > 1000 (agência |X| conta)) |X| depositante)
Exemplo3: Examinando uma subexpressão interna:
σcidade_agência = “Brooklyn” ∧ saldo > 1000 (agência |X| conta))
σcidade_agência = “Brooklyn” (σ saldo > 1000 (agência |X| conta))
σcidade_agência = “Brooklyn” (agência) |X| σ saldo > 1000 (conta)
Exemplo 4: Usando projeções
πnome_cliente (( σcidade_agência = “Brooklyn” (agência) |X| conta) |X| depositante)
πnome_cliente((πnúmero_conta(( σcidade_agência = “Brooklyn” (agência)) |X| conta)) |X| depositante)
Ordenando Junções
Uma boa ordenação de operações de junção é importante para reduzir o tamanho
dos resultados intermediários.
πnome_cliente (( σcidade_agência = “Brooklyn” (agência)) |X| conta |X| depositante)
Poderíamos executar conta |X| depositante primeiro e, então, fazer a junção do
resultado com:
σcidade_agência = “Brooklyn” (agência).
Entretanto, conta |X| depositante provavelmente é uma relação grande, já que contém
uma tupla para cada conta. Em contrapartida,
σcidade_agência = “Brooklyn” (agência) |X| conta
é, provavelmente, uma relação pequena.
Para confirmar, observe que, como o banco tem um grande número de agências
amplamente distribuídas, é provável que apenas uma fração pequena dos clientes do
banco tenha conta em agências localizadas no Brooklyn. Assim, a expressão precedente
resulta em uma tupla para cada conta mantida em uma agência localizada no Brooklyn.
Então, a relação temporária que precisa ser armazenada é menor que a que se obteria
fazendo primeiro conta |X| depositante.
Otimização Heurística
Uma árvore de consulta pode ser transformada passo a
passo em outra árvore de consulta mais eficiente.
Entretanto é preciso assegurar que os passos de
transformação sempre levem a uma árvore de consulta
equivalente.
Determinadas regras de transformação preservam essa
equivalência.
Algoritmo de Otimização Algébrica
Passo1:
A regra 1, ao ser usada, quebra quaisquer
operações SELECT com condições conjuntivas em
uma cascata de operações SELECT, permitindo
um maior grau de liberdade para transferir
operações SELECT para ramos diferentes e
abaixo na árvore.
Passo2:
Usando as regras 2, 4, 6, e 10 relativas à
comutatividade do SELECT com outras operações,
move cada operação SELECT o mais longe para
baixo na árvores, que forem permitido pelos
atributos envolvidos na condição de seleção.
Algoritmo de Otimização Algébrica
Passo 3:
Usando as regras 5 e 9, relativas à comutatividade e
associatividade de operações binárias, rearraja os
nós folhas da árvore utilizando o seguinte critério:
Posiciona as relações do nó folha com operações de
SELECT mais restritivas, de forma que elas possam ser
executadas o quanto antes.
Algoritmo de Otimização Algébrica
Passo 4:
Usando a regra 12, combina uma operação de
PRODUTO CARTESIANO com uma operação
SELECT subseqüente na árvore, gerando uma
operação JOIN se a condição representa uma
condição de junção.
Passo 5:
Usando as regras 3, 4, 7 e 11, relativas à cascata de
PROJECT e à comutação de PROJECT com outras
operações, quebra e transfere as listas de atributos
de projeção para baixo na árvore.
Algoritmo de Otimização Algébrica
Passo 6:
Identifica subárvores que representam grupos de
operações que podem ser executadas por um único
algoritmo (execuções em pipeline).
Como exemplo, considere a consulta:
select unome
from Empregado, Trabalha_Em, Projeto
where pnome = ‘Aquarius’ and pnumero = pno and
essn = ssn and datanasc > ’31-12-1957’;
Exemplo:
Passos na conversão de uma
árvore de consulta durante a
otimização heurística. (a) Árvore
de consulta inicial (canônica) para
a consulta. (b) Transferência das
operações SELECT para baixo na
árvore de consulta. (continua)
Exemplo:
Passos na conversão de uma
árvore de consulta durante a
otimização heurística. (c)
Aplicação, em primeiro lugar, da
operação SELECT mais restritiva.
(d) Substituindo PRODUTO
CARTESIANO e SELECT por
operações JOIN.
Exemplo:
Passos na conversão de uma
árvore de consulta durante a
otimização heurística. (e)
Transferência das operações
PROJECT para baixo na árvore
de consulta.
A Escolha de Planos de Avaliação
A geração de expressões é apenas parte do processo
de otimização de consultas.
Um plano de avaliação define exatamente qual algoritmo
será usado para cada operação e como a execução das
operações é coordenada.
π nome_cliente (classificar para remover duplicatas)
|X| (hash-junção)
|X| (merge_junção)
σcidade_agência = Brooklyn
σ(use o índice 1)
agência
depositante
σ saldo < 1000
(use a varredura linear)
conta
Interação de Técnicas de Avaliação
Um modo de escolher um plano de avaliação para uma expressão de
consulta é simplesmente escolher o algoritmo mais barato para avaliar
cada operação. E, olhando para os níveis da árvore, escolhe-se qualquer
ordenamento para a execução das operações, desde que as operações
nas camadas mais baixas da árvore sejam executadas antes das
operações nas camadas mais altas.
Entretanto, essa estratégia pode não ser a melhor. Embora uma mergejunção, sob certas condições, possa ser mais cara que uma hash-junção,
ela consegue prover um resultado classificado que torna mais barata a
avaliação de uma operação posterior (como uma eliminação de duplicatas
ou uma outra merge-junção).
Para escolher o melhor algoritmo global, se deve considerar até mesmo os
algoritmos que não são os melhores para as operações individuais.
Abordagens para escolha do melhor plano de avaliação:
Baseada no custo de todos os planos;
Heurística.
Otimização Baseada em Custo
O otimizador baseado no custo gera uma faixa de planos de
avaliação a partir de uma determinada consulta usando as regras
de equivalência e escolhe aquele de menor custo.
Para uma consulta complexa, o número de planos diferentes por ser
muito grande.
Diferentes técnicas podem ser usadas para diminuir o número de
planos a serem avaliados:
Quando se examina os planos para uma expressão, é possível
terminar após examinar apenas uma parte da expressão, se for
determinado que o plano mais barato para aquela parte já está
mais caro que a avaliação mais barata para uma expressão
completa já examinada.
Otimização Heurística
Regras heurística são utilizadas para
transformar consultas da álgebra relacional;
No otimizador heurístico, a estratégia mais
eficiente para cada operação é escolhida.
Estrutura dos Otimizadores de Consulta
Na prática, a maioria dos otimizadores de consultas combinam
estratégias baseadas em custo com estratégias heurísticas.
Alguns SGBDs consideram a probabilidade de já haver no
buffer a página que contém o dado que se está precisando
(isso é mais um dado estatístico e pode resultar em estimativas
de custos menores).
É possível usar a estratégia heurísticas de dividir as consultas
em sub-consultas que não utilizem mais de duas tabelas.
Transforma consultas em SQL em outras consultas em SQL
que utilizam junções onde for possível facilita a transformação
da consulta em SQL em uma consulta em álgebra relacional.
Otimização em PostgreSQL
Seguem alguns breves comentários sobre otimização e
desempenho em PostgreSQL.
Para otimização e desempenho, PostgreSQL utiliza-se
dos comandos vacuum, analyze e explain.
Otimização em PostgreSQL
VACUUM O comando Vacuum tanto recupera
espaço em disco, quanto otimiza o desempenho do
banco e previne contra perda de dados muito antigos,
devido ao recomeço do ID das transações. Portanto,
deve ser utilizado constantemente, pois também
atualiza as estatísticas dos dados utilizados pelo
planejador de comandos.
Na linha de comando:
vacuumdb -faze ou vacuumdb -fazq.
Otimização em PostgreSQL
Exemplo de uso do vacuum:
Em uma tabela:
VACUUM VERBOSE ANALYZE nometabela;
Em um banco de dados completo:
Somente VACUUM ou VACUUM FULL ANALYZE;
Recomendações:
Para a maioria das instalações executar o comando VACUUM
ANALYZE para todo o banco de dados, de vez em quando, em horário
de pouca utilização.
Quando for excluída a maioria dos registros de uma tabela, sugere-se a
execução do comando VACUUM FULL. Contudo, este comando gera
um forte bloqueio nas tabelas em que é executado.
Otimização em PostgreSQL
ANALYZE O comando ANALYZE coleta estatísticas
sobre o conteúdo das tabelas do banco de dados e armazena os
resultados na tabela do sistema pg_statistic. Posteriormente, o
planejador de comandos utiliza estas estatísticas para ajudar a
determinar o plano de execução mais eficiente. Caso estas
estatísticas não sejam atualizadas com freqüência, pode ser
comprometido o desempenho do banco de dados, por uma
escolha errada do plano de execução dos comandos.
Normalmente, operações DELETE ou UPDATE não removem
os registros automaticamente (somente após a execução do
vacuum isso acontece).
Otimização em PostgreSQL
No PostgreSQL, pode ser utilizado o comando
explain para ver o plano (conjunto executável de
instruções) criado pelo sistema para qualquer
comando.
O comando explain traz informações sobre custo,
como tamanho da tupla, custo estimado de execução,
entre outros.
Exemplo de uso do explain:
EXPLAIN SELECT * FROM NOMETABELA;
Otimização em PostgreSQL
EXPLAIN ANALYZE Executa a consulta e
mostra o tempo real acumulado dentro de cada
nó do plano de execução, junto com os custos
estimados que o comando explain simples
mostraria.
Referências Bibliográficas
Sistemas de Banco de Dados. (Cap. 12) Abraham
Silberchatz, Henry F. Korth e S. Sudarshan. 3ª Edição.
Makron Books, 1999.
Sistemas de Banco de Dados. (Cap. 15) Ramez
Elsmari, Shamkant B. Navathe. 4ª Edição. Pearson
Addison Wesley, 2005.
Manuais do PostgreSQL.
Download

Otimização de Consultas