Artigo - Técnico Lisboa

Propaganda
Tecnologias de Base de Dados
2005/2006
PROCESSAMENTO DE INTERROGAÇÕES
EM MEMÓRIA PRINCIPAL
por Vitor Manuel G. da Silva
Sumário. Apresentam-se, de seguida, alguns dos algoritmos de processamento de interrogações em memória
principal e análise do seu desempenho, assim como quais são os seus prós e contras tendo em conta alguns dos
cenários possíveis das operações de manipulação de dados do dia a dia.
Palavras-chave. Interrogações, Memória Principal, Árvores, Hash, Select, Join, Aggregation, Projection,
Ordenação.
1. Introdução
As interrogações SQL permitem a um utilizador
colocar questões a uma base de dados de modo a
obter as respostas que necessita. Desse modo faz-se
com que a base de dados não seja um simples
repositório de dados, mas sim uma ferramenta
importante, utilizada no dia a dia pelos utilizadores
que precisam de lidar com a informação nela contida.
O processamento de interrogações é fundamental num
Sistema de Gestão de Base de Dados. Para que o
desempenho de uma base de dados seja aceitável, esta
deverá responder às solicitações dos utilizadores no
menor espaço de tempo possível. Todas as
interrogações têm de ser executadas em memória
principal para permitir manipular os dados, de modo a
obter os resultados. Contudo as bases de dados actuais
ocupam um espaço de armazenamento cada vez maior
já chegando aos terabytes e, apesar da memória
principal já apresentar preços mais acessíveis do que
há uns anos atrás, estes ainda não são comparáveis
aos custos da memória secundária. Para além disto
não podemos negligenciar o facto de que quanto
maior a capacidade disponível para implementar
sistemas de base de dados mais informação se
“consegue arranjar” para preencher essa capacidade.
Com base no paradigma de que a memória secundária
se destina a armazenar os dados e a memória principal
a processar interrogações, foram desenvolvidos
diversos algoritmos que procuram (i) minimizar o
tempo para fornecer os resultados de uma
interrogação, e (ii) minimizar o espaço necessário, em
memória principal, para produzir os resultados. [5]
O modelo relacional é composto por um conjunto de
operadores que podem ser implementado com recurso
a diversos algoritmos, alguns mais eficientes, outros
nem tanto. Iremos agora analisar alguns desses
operadores.
2. Selecção (Select)
A selecção é um operador relacional cujo objectivo é
aceder ou extrair os tuplos de uma tabela que
obedeçam a uma condição. Por exemplo, dada uma
tabela de alunos, mostrar quais os nomes dos alunos
que têm 24 anos de idade traduz-se numa
interrogação que poderia ser escrita da seguinte forma
em SQL:
Select nome
From Alunos
Where idade = 24
Para poder processar uma interrogação, o primeiro
passo é colocar os tuplos da tabela em memória
principal para que possam ser manipulados. Assim
deverá começar-se por identificar e escolher quais
serão as melhores estruturas de dados em termos de
economia de espaço e de rapidez no acesso. Estas
estruturas de dados terão de permitir a “navegação”
nos dados, ou seja, deverá ser possível aceder a
qualquer elemento que esteja nessa estrutura de
dados, bem como adicionar ou remover um desses
elementos. Uma vez que a operação de selecção
consiste em aceder a um ou mais tuplos que
obedeçam a uma dada condição, o acesso aos dados
armazenados nestas estruturas constitui uma
importante porção do processamento necessário para
obter os resultados. [1,3]
Nesta secção iremos analisar o seguinte conjunto de
algoritmos: os Arrays, as AVL Trees (Árvores
Binárias), as B+ Trees, as T Trees, o algoritmo de
Chained Bucket Hashing, o Extendible Hashing, o
Linear Hashing, ou o algoritmo de Modified Linear
Hashing.
2.1 Arrays
Os Arrays constituem a estrutura de dados que
melhores resultados obtém ao nível do espaço
ocupado. De facto, trata-se de uma sequência
ordenada e contígua de elementos de dados cuja
Tecnologias de Base de Dados
1
Tecnologias de Base de Dados
dimensão é dada pelo número de elementos que se
pretende guardar. Cada elemento do array contém um
campo de dados, sendo necessário apenas mais um
ponteiro que indique a posição inicial do array.
Contudo os Arrays apresentam um desempenho muito
pobre ao nível da inserção e remoção de dados visto
que o reordenamento do array é um processo muito
lento. Em média, uma actualização num array implica
mover cerca de metade dos elementos do array com
vista à sua ordenação. A pesquisa de um elemento
num array ordenado é uma tarefa razoavelmente
eficiente e os seus tempos de resposta não são muito
elevados. [1,5]
2.2 Adelson-Velsky Landis Tree (Árvores Binárias)
As Árvores Binárias são estruturas de dados
caracterizadas por uma representação, tal como o
nome indica, em árvore, em que cada elemento,
denominado nó, é constituído pelo elemento de dados
que se pretende armazenar, bem como por dois
ponteiros, cada um deles a apontar para outros dois
elementos, tal como a Figura 1 indica. [1,5]
Figura 1 - Um nó de uma árvore binária
Uma árvore é binária precisamente por cada nó se
dividir em dois nós, ficando a árvore final com uma
representação similar à que se segue na Figura 2.
2005/2006
A inserção de elementos nesta estrutura de dados
passa por criar um novo nó que deve conter os dados
que se queiram guardar. De seguida, serão
preenchidos os campos dos ponteiros de forma a
garantir que a árvore permanecerá ordenada após a
inserção de um novo elemento de dados. Esta
reordenação poderá, no limite, implicar a
reestruturação de toda a árvore, pelo que este é um
ponto crítico da inserção, em termos de tempo. Deste
modo o tempo de inserção depende da reordenação
que é necessária efectuar. [1,2,5]
Na remoção, o processo é análogo. Um elemento é
removido quando nenhum nó na árvore tem um
ponteiro a referenciá-lo. A remoção é rápida, em si,
mas a reordenação que poderá ter que ser feita
constituirá a grande fatia do tempo necessário para a
operação. [1,2,5]
No que diz respeito à operação de selecção que
estamos a analisar, o que interessa é o tempo de
resposta do algoritmo de pesquisa de dados na árvore
binária. A pesquisa numa árvore binária começa por,
a partir do nível mais alto da árvore, a raiz, verificar
se esse é o elemento pretendido. Caso não o seja, a
pesquisa seguirá pelo ramo da esquerda e assim
sucessivamente em cada nó. Caso chegue ao final do
ramo da esquerda e não encontre o elemento
pretendido, começará a analisar os ramos da direita.
Trata-se de uma pesquisa baseada num algoritmo de
procura em profundidade que é bastante eficiente e
que, tipicamente, apresenta bons resultados. [1]
Apesar dos problemas já referidos ao nível do tempo
de inserção e remoção de elementos, provavelmente o
maior entrave à adopção desta estrutura de dados será
o seu custo em termos de espaço. De facto, para cada
nó, além dos dados a guardar, que poderá ser um
simples ponteiro, terão de se guardar mais dois
ponteiros para os nós filho. Ou seja, na melhor das
hipóteses, uma árvore binária ocupará sensivelmente
o triplo do espaço ocupado pelos dados que se
pretende guardar. [1,2]
2.3 B+ Tree
Figura 2 - Uma árvore binária balanceada
Outra característica desta estrutura de dados é o facto
dos seus elementos da árvore estarem ordenados de
alguma forma, e da árvore ser balanceada, ou seja,
cada nó que não pertença ao nível inferior da árvore
(nó não folha) deve ter ligação a dois nós. [1,5]
Tecnologias de Base de Dados
As B+ Trees surgiram precisamente para procurar
reduzir o espaço desperdiçado pelas Árvores Binárias
passando cada um dos nós a armazenar mais do que
um elemento (Figura 3). Desta forma reduz-se quer a
dimensão da árvore quer o espaço ocupado por esta
(Figura 4). [1,5]
2
Tecnologias de Base de Dados
Figura 3 - Um nó de uma B+ Tree
2005/2006
propriamente lenta apresenta uma desvantagem em
relação às Árvores Binárias: a pesquisa nos elementos
de cada nó. Ao efectuar uma pesquisa numa Árvore
Binária, ao analisar um nó para ver se este
corresponde ao valor pretendido, caso este não o seja,
imediatamente se segue para a análise do nó seguinte.
Numa B+ Tree, ao pesquisar por um valor quando se
analisa um nó tem que se analisar individualmente
cada um dos elementos que compõem o nó e, caso o
valor pretendido não esteja entre eles terá de se
analisar o nó seguinte. Ora é precisamente para
procurar optimizar esta avaliação dos elementos de
um nó que surgem as T Trees. Para tal, cada nó de
uma T Tree tem mais dois campos: o valor mínimo do
nó e o valor máximo do nó. [1,5]
Figura 4 - Exemplo de uma B+ Tree
No que toca ao algoritmo de pesquisa na B+ Tree a
principal diferença para as árvores binárias passa por,
em cada nó se comparar os diversos elementos que o
compõem com a condição que se pretende verificar.
Caso o elemento pretendido não exista nesse nó
prosseguirá a pesquisa pelo ramo onde haja mais
hipóteses de se encontrar o elemento pretendido, ou
seja, para cada elemento de um nó é feita uma
comparação entre o valor pretendido e o valor desse
elemento. Caso o valor pretendido seja inferior ao
valor existente seguir-se-á o ramo à esquerda do
elemento. Caso o valor pretendido seja superior ao
valor existente seguir-se-á o ramo da direita e assim
sucessivamente até alcançar o registo pretendido.
[1,5]
Quer o algoritmo de inserção, quer o de remoção de
elementos acarretam, à semelhança da árvore binária,
um custo de tempo assinalável devido à necessidade
de reordenação da árvore. Contudo, uma vez que cada
nó pode ter vários elementos e, um nó pode ter
elementos vazios, o reordenamento poderá não ser
necessário. Uma vez que a reordenação não acontece
sempre que um elemento é inserido ou removido, as
B+ Trees apresentam resultados melhores que as
Árvores Binárias. Os algoritmos de inserção e
remoção só por si não são mais eficazes ou simples,
simplesmente a componente de reordenação ocorre
menos vezes. [1,2,5]
O objectivo das B+ Trees de reduzir o espaço
ocupado pelas árvores binárias é alcançado. Pois ao
invés de dois ponteiros por cada elemento de dados da
Árvore Binária, numa B+ Tree só é necessário um
ponteiro para referenciar o nó seguinte, mais o
número de ponteiros suficiente para fazer as ligações
entre os nós folhas. [1,2]
2.4 T Trees
As T Trees procuram optimizar a pesquisa das B+
Trees. Se bem que a pesquisa nas B+ Trees não seja
Tecnologias de Base de Dados
Figura 5 - Um nó de uma T Tree
Quando uma pesquisa está ser efectuada num nó
compara-se o valor mínimo e máximo desse nó com o
valor pretendido. Caso o valor pretendido esteja
dentro do intervalo desse nó então pesquisar-se-á
nesse nó pelo valor que se procura. Caso o valor
pretendido não esteja nesse nó imediatamente a
pesquisa avançará para o nó seguinte: se o valor a
pesquisar for menor que o valor mínimo do nó a
pesquisa prossegue pelo ramo da esquerda, caso o
valor pretendido seja superior ao máximo desse nó a
pesquisa segue para o ramo da direita. [1,5]
Figura 6 – Uma T Tree
As T Trees procuram juntar o melhor das Árvores
Binárias e das B+ Trees: a rapidez na decisão do
ramo a seguir e a capacidade de armazenamento de
cada nó. Os algoritmos de inserção e remoção, tal
como nas B+ Trees, não são mais eficazes só por si,
mas por implicarem menos frequentemente a
reordenação da árvore. Em termos de espaço, o
desempenho é similar às B+ Trees, já que apesar de
3
Tecnologias de Base de Dados
necessitar de guardar mais dois campos de controlo
(valor máximo e mínimo do nó), não necessita dos
ponteiros de ligação entre os nós folha, à semelhança
das Árvores Binárias, o que acaba por “equilibrar” as
contas. [1,5]
2.5 Chained Bucket Hashing
Chained Bucket Hashing é uma estrutura de dados da
família dos algoritmos de hashing, em que é utilizada
uma função matemática para dividir o conjunto dos
dados em subconjuntos mais pequenos, denominados
buckets (baldes), cuja consulta seja mais rápida e
eficiente. Assim, os algoritmos de hashing procuram
seguir a filosofia de “dividir para conquistar”.
Estes subconjuntos de dados são guardados em
tabelas de hash (arrays) em que cada célula contém a
identificação do subconjunto e um ponteiro para um
balde que conterá os dados em concreto. Os dados,
por sua vez, estão armazenados em baldes que por sua
vez estão ligados entre si através de listas
simplesmente ligadas, daí o nome de Chained Bucket
(Baldes Ligados). [1,5]
2005/2006
desempenho da pesquisa está mais dependente da
função de hash, dado que uma boa função de hash
produzirá uma distribuição mais uniforme dos dados
pelos diversos nós, do que da pesquisa na lista ligada.
[1]
No que toca ao espaço, o desempenho não é muito
satisfatório. Para além do espaço ocupado pelos
dados, temos o espaço ocupado pelos ponteiros para o
próximo balde. Adicionalmente temos ainda o espaço
ocupado pela tabela de hash. No total, tipicamente,
uma tabela de hash com baldes ligados ocupa
sensivelmente o dobro do espaço dos dados que se
pretende guardar (caso os dados a guardar também
tenham a dimensão de ponteiros). [1]
Um dos grandes inconvenientes do Chained Bucket
Hashing é o facto de ser uma estrutura de dados de
dimensão fixa no que toca à tabela de hash. Esta tem
de ser dimensionada à partida e com cuidado para
obter um bom desempenho. Poucas entradas na tabela
de hash farão com que cada nó tenha muitos baldes.
Uma tabela de hash muito grande pode, para além do
custo de espaço adicional, aumentar as hipóteses de
não se conseguir obter uma distribuição uniforme. [1]
2.6 Extendible Hashing
Um dos algoritmos que procura ultrapassar a
limitação da dimensão fixa do Chained Bucket
Hashing é o algoritmo de Extendible Hashing que
implementa uma tabela de hash que cresce
dinamicamente com a quantidade de dados.
Figura 7 – Exemplo de uma estrutura de dados
Chained Bucket Hashing
O Chained Bucket Hashing tem um bom desempenho
ao inserir e remover elementos ou a efectuar
pesquisas, embora esta eficácia seja à custa de espaço
de armazenamento. A inserção de elementos na
estrutura de dados começa por verificar em que nó da
tabela de hash deverá ser inserido o elemento, através
da aplicação da função de hash. Depois o elemento é
inserido num balde na lista ligada associada ao nó da
tabela de hash. A remoção consiste em simplesmente
fazer com que o ponteiro que referencia o elemento
que se pretende remover deixe de o referenciar. [1,5]
A pesquisa começa por identificar qual o nó da tabela
de hash em que o dado pretendido estará, aplicando
para tal a função de hash ao valor da condição. Uma
vez identificado o nó bastará percorrer os baldes de
dados até encontrar o que se pretende. Na prática, o
Tecnologias de Base de Dados
Uma das principais diferenças relativamente ao
Chained Bucket Hashing diz respeito ao conteúdo de
cada nó. Nas tabelas de hash com baldes ligados, a
função de hash pode ser uma qualquer que subdivida
os dados. No Extendible Hashing é utilizada a
representação binária dos elementos de dados. Por
exemplo, se o elemento de dados a inserir for o
número 9, a sua representação binária será 1001. Este
elemento poderá então ser inserido num balde que
contenha elementos cuja representação binária
termine em 01, por exemplo. Ao utilizar a
representação binária, a probabilidade de baldes
vazios é menor porque só existem dois algarismos
possíveis, em vez dos dez da representação decimal
ou dos vinte e seis da representação alfabética. [1,5]
O número de elementos que cada um dos baldes pode
conter é limitado, pelo que quando existe a
necessidade de adicionar um elemento e o balde já se
encontra totalmente preenchido, acontece um
overflow. Quando acontece um overflow, o balde sem
mais espaço é dividido em dois e os elementos de
dados são redistribuídos pelos dois de acordo com a
função de hash. A operação de overflow poderá ainda
4
Tecnologias de Base de Dados
necessitar de aumentar a tabela de hash de modo a
comportar os novos baldes. [1,3,5]
2005/2006
Figura 9 – Estrutura de dados após a inserção do
elemento 20
Para inserir o elemento de dados 9 na estrutura de
dados da Figura 9 irá acontecer um novo overflow, no
balde B. Este terá de ser divido em dois (B e B2) e os
elementos de dados redistribuídos. Novamente
passarão a ser contabilizados três dígitos (001 e 101)
em vez de apenas dois, mas desta vez a tabela de hash
não precisará de ser redimensionada. A Figura 10
mostra o resultado da inserção do elemento 9. [3]
Figura 8 – Uma estrutura de dados Extendible
Hashing
A Figura 8 representa um exemplo de uma estrutura
de dados Extendible Hashing com alguns elementos
de dados já inseridos. Se tentarmos inserir o elemento
de dados 20, cuja representação binária é 10100, irá
acontecer um overflow porque o balde A já se
encontra totalmente preenchido. O balde A terá de ser
dividido (A e A2) e os dados terão de ser
redistribuídos pelos dois baldes. Para que os dados
sejam redistribuídos já não bastará olhar para dois
dígitos da representação binária, sendo necessário
olhar para três dígitos (32 = 100000, 4 = 100).
Contudo a tabela de hash apenas comporta dois
dígitos (00, 01, 10 e 11) pelo que terá de ser
aumentada. A Figura 9 mostra o resultado dessa
operação tendo a tabela de hash duplicado e o balde
A dividido em dois. [3]
Figura 10 – Estrutura de dados após a inserção do
elemento 9
A pesquisa no Extendible Hashing é feita de forma
similar às tabelas de hash com baldes ligados mas
tenderá a apresentar melhores resultados com
quantidades de dados elevadas pois os baldes são de
dimensão fixa e tipicamente inferior à dimensão dos
baldes ligados. [1,3,5]
No que toca à inserção e remoção de elementos a
tarefa de redimensionar a tabela de hash inflacionará
o tempo e influenciará o desempenho do Extendible
Hashing. Deste modo, o Extendible Hashing
apresenta um desempenho bastante bom quer ao nível
da pesquisa quer ao nível das suas actualizações,
desde que estas não signifiquem muitos
redimensionamentos da tabela de hash. Contudo este
desempenho paga-se em termos de espaço. O
redimensionamento da tabela de hash é uma tarefa
com um custo elevado ao nível de armazenamento
dado que um nó e um balde, em caso de overflow, dão
origem a pelo menos dois baldes, e em caso de
aumento da tabela de hash, a dois nós, que poderão
não vir a ser utilizados na sua totalidade. [1,3,5]
Tecnologias de Base de Dados
5
Tecnologias de Base de Dados
2.7 Linear Hashing
Uma outra variante dos algoritmos de hash é o Linear
Hashing. O conceito desta estrutura de dados passa
por, uma vez que a tarefa de redimensionamento tem
custos computacionalmente elevados, procurar
estabelecer
critérios
adicionais
para
o
redimensionamento da estrutura de dados ao nível de
armazenamento e não apenas por overflow de um
balde. A ideia é efectuar o redimensionamento não
apenas por acontecer um overflow num baldes, mas
sim efectuá-lo de uma forma mais eficaz, em termos
de espaço, ou seja, quando o número de overflow
tornar conveniente um novo balde. [1,5]
Outra característica que diferencia o algoritmo de
Linear Hashing dos restantes algoritmos de hash é o
facto de não ser utilizada uma tabela de hash. Os
baldes são colocados em memória de forma
consecutiva. Deste modo é possível calcular a
localização de qualquer balde a partir do endereço
inicial, um pouco à semelhança de um array. Tornase também mais simples analisar o crescimento da
estrutura de dados e dos custos de armazenamento.
[1,3,5]
2005/2006
os dados. Além disso, esperar o momento em que é
conveniente criar um novo balde acarreta custos em
termos de espaço, dado que é necessário guardar as
páginas de overflow que contém os elementos de
dados que serão inseridos nos baldes. [1,3,5]
2.8 Modified Linear Hashing
O Linear Hashing, como explicado na secção 2.7,
procura optimizar o espaço de armazenamento mas à
custa do seu desempenho. Para procurar “corrigir”
esse problema existe o Modified Linear Hashing que
procura utilizar o critério do desempenho para o
redimensionamento.
O Modified Linear Hashing recorre novamente a
tabelas de nós que referenciam os baldes (tabela de
hash) que se encontram simplesmente ligados,
procurando juntar conceitos do Chained Bucket
Hashing com conceitos do Extendible Hashing. O
Modified Linear Hashing apresenta uma estrutura de
baldes ligados, mas cada nó utiliza a representação
binária para referenciar os dados. A Figura 12 ilustra
uma tabela do Modified Linear Hashing. [1,5]
A Figura 11 representa o aumento do número de
baldes de uma estrutura de Linear Hashing. Já
existem overflows suficientes para dividir o balde 00
em dois (000 e 100). [1,3,5]
Figura 12 – Uma estrutura de dados Modified Linear
Hashing
O critério para redimensionar a tabela de hash é a
dimensão das listas de baldes, dado que é o factor que
influencia o desempenho das pesquisas e
actualizações de dados numa estrutura de dados com
baldes ligados. [1,5]
Figura 11 – Crescimento da estrutura de dados Linear
Hashing
A pesquisa em estruturas Linear Hashing é eficaz
porque os baldes estão ordenados sequencialmente e o
algoritmo de pesquisa tem acesso à informação
necessária para calcular a localização do balde que
contém o elemento de dados pretendido a partir da
localização inicial. [1,3,5]
Desta forma o desempenho do algoritmo nas
pesquisas é bastante bom. Quanto às actualizações,
assim que o volume de dados a guardar começa a
aumentar e a implicar muitos redimensionamentos o
desempenho começa a degradar-se. O volume da
informação a guardar é determinante para o
desempenho da estrutura de dados. [1,5]
No que toca ao espaço de armazenamento o resultado
é similar ao do Chained Bucket Hashing, ou seja, o
Modified Linear Hashing poderá chegar a ocupar
cerca do dobro do espaço ocupado somente pelos
dados (caso os dados também sejam da dimensão de
um ponteiro). [1,5]
Na prática, os resultados alcançados por esta estrutura
de dados são insatisfatórios, devido ao seu custo de
actualização dos dados. Para manter uma utilização
do espaço eficaz, é perdido muito tempo a reorganizar
Tecnologias de Base de Dados
6
Tecnologias de Base de Dados
2005/2006
2.9 Comparação do desempenho das Estruturas de
Dados
3. Junção (Join)
Depois de analisar individualmente cada uma destas
estruturas de dados é importante fazer uma
comparação entre os seus desempenhos.
Existe uma primeira divisão entre os algoritmos de
funções de hash e os algoritmos de árvores. Os arrays
são colocados de parte dado que, apesar de serem a
estrutura de dados com melhor desempenho em
termos de armazenamento, o desempenho em termos
de velocidade de pesquisa e principalmente de
actualização não os torna viáveis para serem
adoptados como estrutura de dados eficiente, salvo
em casos em que o volume de dados a guardar seja
mesmo baixo. [1]
No que toca aos algoritmos de manipulação de
funções de hash, qualquer um deles apresenta um
bom desempenho ao efectuar uma pesquisa. No que
toca às actualizações, à excepção do Linear Hashing
são eficientes. No caso do Linear Hashing, a sua
optimização em termos de espaço tem como custo o
seu desempenho em termos de velocidade de
actualização.
Em
termos
de
espaço
de
armazenamento, a estrutura de dados que ocupa
menos espaço é precisamente o Linear Hashing. O
espaço ocupado pelas restantes estruturas de dados
deixa algo a desejar podendo ocupar o dobro do
espaço realmente ocupado pelos dados. [1]
A análise aos algoritmos de árvores mostra que os
desempenhos alcançados pelas B+ Trees e T Trees
quer em termos de pesquisa, quer em termos de
actualização e custo de armazenamento, são bons.
Apenas as Árvores Binárias apresentam um
desempenho fraco em termos do custo de
armazenamento, podendo ficar a ocupar o triplo do
espaço ocupado pelos dados nela guardados. [1]
Não se pode dizer, com certeza, que uma estrutura de
dados é melhor que outra em todas as situações
possíveis. Os Sistemas de Gestão de Base de Dados,
ao processar uma interrogação terão de optar pelo que
indicie melhor desempenho, tendo de ser ponderados
certos parâmetros como a quantidade de dados a
guardar e/ou a pesquisar, o espaço disponível, entre
outros.
Por último temos o facto de que uma interrogação
SQL com um Select pode ser de dois tipos: de
igualdade ou desigualdade. É consensual que os
algoritmos de funções de hash apresentam melhor
desempenho para tratar igualdades. Os algoritmos de
árvores são mais adequados a desigualdades.
Tecnologias de Base de Dados
Outro tipo de interrogações SQL é a junção, que
consiste em seleccionar informação de duas tabelas
ou mais, de acordo com pelo menos um atributo
comum, como por exemplo:
Select A.nome, N.nota
From Alunos A, NotasTBD N
Where a.numero = n.numeroAluno
Para tal existem diversos algoritmos entre os quais se
incluem: o Nested Loops Join, o Sort Merge Join ou o
Tree Merge Join, o Hash Join ou o Tree Join.
Nesta secção analisaremos o desempenho destes
algoritmos ao nível do tempo de cálculo dos
resultados. Para tal, são tidos em consideração
aspectos como por exemplo a dimensão das tabelas,
variando a dimensão das relações, a percentagem de
duplicados existente nas tabelas e a sua distribuição
(uniforme ou enviesada).
3.1 Nested Loops Join
A primeira aproximação possível ao cálculo de
resultados de junções é o Nested Loops Join que
consiste em, para cada um dos tuplos de uma tabela,
percorrer todos os tuplos da outra tabela e verificar
um a um quais os tuplos que obedecem à condição de
igualdade. [1]
Este algoritmo tem um tempo de execução máximo
para a situação em que ambas as tabelas têm o mesmo
número de tuplos, de O (N²) em que N é o número de
tuplos de uma tabela. Mesmo para relações com
poucos tuplos (da ordem das centenas ou milhares) o
tempo dispendido no processamento não permite que
o Nested Loops Join seja considerado como muito
eficiente. [1]
3.2 Sort Merge Join
A ideia base do Sort Merge Join é a ordenação prévia
das relações para procurar que tuplos com valores
iguais fiquem contíguos. Deste modo os valores
iguais ficam agrupados em partições. Assim poder-seão comparar as partições em vez dos tuplos
individuais. Os benefícios desta aproximação só serão
sentidos se as tabelas contiverem valores duplicados.
Para efectuar o Sort Merge Join recorre-se a arrays e
ordenam-se as relações recorrendo ao algoritmo de
ordenação em memória principal quicksort. [1]
7
Tecnologias de Base de Dados
Dadas duas relações R e S, o algoritmo começa por
ordenar as duas relações e identificar as partições. De
seguida, é feita uma pesquisa com o primeiro tuplo de
cada relação. Vai se avançando na relação R enquanto
o tuplo de R for menor que o tuplo de S,
aproveitando-se assim a ordenação previamente
efectuada. Analogamente, avança-se na relação S
enquanto o valor do atributo de join for menor que o
valor de R. Vai se alternando a pesquisa até se
encontrar os valores pretendidos. [1,3,4]
O algoritmo de Sort Merge Join mostra-se eficiente
para tratar junções de relações com um valor elevado
de duplicados, quer estes existam numa distribuição
uniforme, quer numa distribuição enviesada. Esta
característica deve-se ao facto de se agruparem os
valores duplicados em partições. Em relações em que
o número de valores duplicados não seja muito
significativo, o desempenho em termos de tempo do
Sort Merge Join não é tão bom devido à ordenação
das relações, que acaba por ser a tarefa mais
demorada. [1,4]
Uma vez que a estrutura de dados utilizada no Sort
Merge Join é o Array, o desempenho em termos de
espaço de armazenamento é eficiente porque o espaço
ocupado por um Array corresponde ao espaço
ocupado pelos elementos de dados. [1]
3.3 Tree Merge Join
O Tree Merge Join, conceptualmente, é semelhante ao
Sort Merge Join com a diferença de que ao invés de
utilizar arrays como estrutura de dados se recorre a
uma T Tree.
2005/2006
3.4 Hash Join
Para efectuar junções podemos também recorrer ao
Hash Join. O Hash Join começa por particionar as
tabelas, à semelhança do Sort Merge Join, mas em
vez de recorrer a um algoritmo de ordenação recorre a
uma função de hash. De seguida, na chamada fase de
probing, aplica-se uma segunda função de hash às
partições das relações. Desta forma teremos a garantia
que os elementos de dados existentes na partição de
uma relação farão junção com os elementos de dados
da partição com o mesmo valor da outra relação. Na
Figura 13 temos um exemplo da fase de probing, em
que é aplicada uma função de hash h2. Após a
aplicação de h2 se R e S tiverem uma partição com o
mesmo valor de hash, isso significa que os elementos
de dados dessa partição poderão ser juntos. [1,3]
Figura 13 – Fase de Probing do Hash Join
O Hash Join tem um desempenho, regra geral,
inferior ao Tree Merge Join se já estiverem criadas as
estruturas de dados T Tree. Contudo, se não existir
pelo menos uma das T Tree o algoritmo de Hash Join
demonstra ser o mais eficiente, porque constrói-se
mais rapidamente uma tabela de hash que uma T
Tree. O Hash Join tem, no entanto, uma limitação que
é o facto de apenas ser aplicável a junções naturais.
[1,2,3,4]
O Tree Merge Join obtém um melhor desempenho
que o Sort Merge Join, desde que o número de
duplicados não seja muito elevado, dado que num
Array os valores duplicados ficam contíguos,
enquanto numa estrutura em árvore terão de ser
seguidos os ponteiros que ligam os elementos de
dados. Outro resultado importante do algoritmo de
Tree Merge Join é que não é afectado pelo facto da
relação interior ser muito maior que a relação
exterior, ou vice-versa. [1]
No que toca ao espaço de armazenamento as tabelas
de hash do Hash Join são eficientes.
Existe uma grande desvantagem na utilização do Tree
Merge Join que é o facto de apenas apresentar
melhores resultados caso as estruturas de dados já
estejam construídas porque o tempo de construção de
uma T Tree é substancialmente superior ao tempo de
construção dos Arrays do Sort Merge Join. [1]
Em termos de desempenho, o Tree Join é inferior ao
Hash Join e, por vezes inferior ao desempenho do
Sort Merge Join com excepção de uma situação. Se
pelo menos uma das relações já estiver inserida numa
T Tree e a outra relação não for muito grande (cerca
de metade dos elementos de dados na T Tree), então o
Tree Join será mais rápido que o Hash Join. O tempo
de construção da segunda T Tree será inferior ao
tempo de construção das duas tabelas de hash. [1]
Em termos de espaço de armazenamento o
desempenho das T Trees não é tão eficiente como os
Arrays, mas é aceitável. [1]
3.5 Tree Join
O Tree Join é um algoritmo similar ao Hash Join,
apenas diferindo no facto de usar uma T Tree como
estrutura de dados.
Novamente o
armazenamento
Tecnologias de Base de Dados
desempenho do
está directamente
espaço
ligado
de
ao
8
Tecnologias de Base de Dados
desempenho das T Trees, que apesar de não serem as
melhores, também não são comprometedoras.
3.6 Comparação
No que toca à operação de junção, tal como com a
selecção, não se pode dizer que exista um algoritmo
que obtenha um desempenho superior a todos os
outros em qualquer situação, mas é possível
caracterizar melhor quais os pontos fortes de cada
algoritmo.
Caso já existam estruturas de dados com os elementos
de dados das relações envolvidas na junção
tipicamente o algoritmo com melhor desempenho é o
Tree Merge. Se isso não acontecer o Hash Join será a
melhor escolha porque o tempo de construir uma
estrutura de hash é inferior ao tempo de construção de
uma árvore. O Sort Merge Join mostra-se uma opção
viável em duas situações: (i) se o número de
duplicados for elevado, (ii) se o volume de tuplos a
analisar for elevado; dado que o Sort Merge Join é o
algoritmo que lida melhor com quantidades grandes
de informação. [1]
2005/2006
duplicados. Enquanto o volume de duplicados for
reduzido, o desempenho é directamente influenciado
pelo tempo de construção da estrutura de dados. O
algoritmo de Hashing tem um custo de construção
linear em relação ao volume de dados. O algoritmo de
Sorting tem um custo de O (|R| log|R|). Quando o
volume de elementos duplicados se torna elevado e o
tempo de construção da estrutura de dados deixa de
ser o factor preponderante, o melhor desempenho
passa a ser alcançado pelo algoritmo de Hashing. O
Hashing elimina os duplicados logo durante a fase de
construção da tabela de hash enquanto o Sorting
apenas o faz após o array já estar ordenado. [1,3]
5. Agregação (Aggregate)
Temos ainda um tipo de interrogações SQL que é a
agregação. Um exemplo simples de uma interrogação
com recurso à agregação é:
Select Avg (idade)
From Alunos
Group By curso
Neste exemplo procura-se obter a média de idades
dos alunos agrupados por curso.
4. Projecção (Projection)
A projecção é outro tipo de interrogações SQL, do
qual se segue um exemplo:
Select Distinct A.nome
From Alunos A
Em que se procura saber quais os nomes, sem
repetição, dos alunos da relação Alunos, sendo que a
tabela Alunos contém mais atributos para além do
nome.
A implementação da projecção passa por duas fases:
(i) remoção dos atributos não desejados, (ii)
eliminação de tuplos duplicados que daí resultem.
Esta segunda parte da remoção de duplicados é a fase
crítica em termos de desempenho. [1,3]
Existem dois tipos de algoritmos que podem ser
utilizados para efectuar projecções: Sorting e
Hashing. O Sorting consiste em introduzir todos os
elementos num array, ordená-los e de seguida
remover os duplicados. O Hashing recorre a uma
função de hash para colocar os elementos numa
estrutura de hash, eliminando logo aí os elementos
duplicados. [1,3]
O critério que decide qual destes dois algoritmos
obterá melhor desempenho é a quantidade de
Tecnologias de Base de Dados
O algoritmo da agregação passa por processar todos
os tuplos da relação e ir guardando informação
adicional que permita calcular o valor final após o
processamento de todos os dados. [1,3]
Para interrogações com group by, a fase de
agrupamento pode ser efectuada com recurso aos
algoritmos de sorting e hashing, com os prós e contras
já referidos na secção 5. [1,3]
6. Conclusões
O objectivo deste artigo é dar a conhecer algumas das
alternativas existentes para processar as interrogações
em memória principal. Estas são algumas das técnicas
à disposição dos programadores de Sistemas de
Gestão de Bases de Dados em termos dos operadores
de interrogações SQL, bem como explicitar alguns
dos pontos fortes e fracos dos algoritmos.
Estas técnicas foram analisadas apenas do ponto de
vista do desempenho e custos de armazenamento
partindo de uma situação em que os dados já estavam
em memória principal. Mas se considerarmos a tarefa
de colocar em memória principal os dados que
estiverem em memória secundária como algo
9
Tecnologias de Base de Dados
constante, alguns destes resultados continuarão a ser
verdade.
Apesar de alguns destes algoritmos já terem décadas
ainda hoje, com a capacidade tecnológica à nossa
disposição, ainda são bastante eficazes.
A identificação dos pontos fortes e fracos (tolerância
a valores duplicados, dimensão das relações, etc.) dos
algoritmos é muito importante. Se conseguirmos
identificar quais os factores que devem ser analisados
aquando da decisão do algoritmo a implementar isso
poderá levar-nos a desenhar planos de processamento
de interrogações mais eficazes. Se conseguirmos
escolher os melhores planos, estaremos a optimizar o
desempenho quer das interrogações, quer das Bases
de Dados no seu todo.
Referências
1.
“Query Processing in Main Memory Database
Management Systems” - Tobin J. Lehman &
Michael J. Carey, 1986
2.
“Implementation Techniques for Main Memory
Database Systems” - David J. de Witt, Randy H.
Katz, Frank Olken, Leonard D. Shapiro, Michael
R. Stonebraker, David Wood, 1984
3.
“Database Management Systems”, 2nd Edition Mcgraw Hill
4.
“Join Processing in Database with Large Main
Memories” - Leonard D. Shapiro, 1986
5.
“A Study of Index Structures for Main Memory
Database Management Systems” - Tobin J.
Lehman, Michael J. Carey
Tecnologias de Base de Dados
10
2005/2006
Download