Clique aqui para ler esse artigo em PDF.imagem_pdf.jpg

capaSQL12.JPG

Clique aqui para ler todos os artigos desta edição

Desnormalização: uma faca de dois gumes

 

por Eduardo Bezerra

Leitura Obrigatória: Artigos do Prof Bráulio - SQL Magazine 6 e 7.

 

Normalizar as tabelas de um banco de dados relacional tem como objetivo prevenir o aparecimento de certas dependências (dependências funcionais não-triviais, parciais, transitivas, multivaloradas ou de junção) nesse banco de dados. O procedimento para se chegar a um banco de dados normalizado consiste em aplicar sucessivamente diversas transformações sobre o esquema desse banco de dados de forma a obter um novo esquema onde as tabelas componentes estejam em conformidade com determinadas restrições pré-estabelecidas. Essas restrições estão associadas às denominadas formas normais.

Uma forma normal consiste em um conjunto de restrições que um esquema de banco de dados deve satisfazer para que se possa afirmar que tal esquema satisfaz àquela forma normal. Conforme detalhado nos artigos do Prof. Bráulio Ferreira (veja a SQL Magazine 6 e 7), existem diversas formas normais. Cada forma normal (à exceção da primeira) engloba as restrições das formas normais anteriores e define restrições adicionais a serem satisfeitas pelo esquema do banco de dados.

Na prática, a normalização é realizada através da fragmentação vertical de uma ou mais tabelas, ou seja, através da criação de duas ou mais novas tabelas para conter colunas que anteriormente pertenciam a uma única tabela. Portanto, de uma forma geral, um esquema normalizado possui mais tabelas do que o seu esquema correspondente não normalizado. A aplicação da normalização a um esquema dá a certeza de que os dados armazenados segundo este esquema estarão livres de diversas anomalias as quais está suscetível um esquema que não esteja normalizado, como a repetição (redundância) descontrolada de dados.

Podemos pensar na normalização como um estado (ou situação) do banco de dados: se todas as tabelas desse banco estão normalizadas, então o banco está normalizado. Nesse sentido, o estado inverso à normalização é chamado de desnormalização, no qual uma ou mais tabelas do modelo lógico do banco de dados são aglutinadas em uma única tabela do esquema relacional. A princípio, a única razão para a aplicação da desnormalização é a de eliminar o custo das junções em operações de seleção sobre as tabelas envolvidas.

Uma análise superficial do parágrafo anterior pode levar o leitor a concluir que a desnormalização sempre aumenta o desempenho no processamento de consultas de seleção. O raciocínio (errôneo, como veremos a seguir) para essa conclusão seria o seguinte: “se a quantidade de tabelas é maior em um esquema relacional normalizado, então haverá um maior número de operações de junção para a obtenção do resultado das consultas nesse esquema do que em um esquema desnormalizado correspondente (operações de junção são sabidamente bastante custosas do ponto de vista computacional). Conseqüentemente, o custo da execução de seleções em um esquema normalizado é sempre maior do que o custo sobre o esquema desnormalizado correspondente”.

Para argumentar a razão de o raciocínio anterior constituir uma falácia, me permitam considerar algumas situações fora do contexto de bancos de dados:

 

·         Ontem houve greve de ônibus no Rio de Janeiro. Fui para o trabalho de carro. A princípio, o que era de se esperar era que eu chegasse mais cedo ao trabalho (afinal de contas, a quantidade de ônibus nas ruas era menor). Acabei por chegar mais tarde. O fato é que, justamente por conta da falta de ônibus, a quantidade de carros de passeio nas ruas aumentou tanto que o trânsito ficou ainda mais caótico do que o usual.

·         A princípio, pode-se pensar que o aumento dos downloads de músicas pela Internet é o principal fator para a queda de venda de CDs. No entanto, um estudo realizado por economistas norte-americanos revela que baixar músicas de um CD não afeta significativamente suas vendas. Segundo o estudo, outros fatores parecem ser a verdadeira causa da queda de vendas: competição com outras mídias (DVDs e vídeo games);  redução do número de discos lançados; boicote dos consumidores contra a indústria fonográfica; crescimento exagerado das vendas de CDs nos anos noventa, quando o uso de CDs em vez de LPs, estava começando a deslanchar; etc.

 

O leitor pode vir a se perguntar o que as situações acima têm a ver com o conceito de desnormalização. O fato é que o mesmo tipo de raciocínio menos cuidadoso que pode levar a conclusões errôneas (assim como nas situações acima) é utilizado na desnormalização. Na verdade, as variáveis com as quais o desenvolvedor encarregado de escolher entre a normalização e a desnormalização tem que lidar são numerosas. Analisada mais detalhadamente, a situação é mais complicada do que parece, e não necessariamente o processamento sobre uma tabela desnormalizada será sempre mais eficiente do que sobre um esquema equivalente normalizado.

O objetivo desse artigo é mostrar que, embora existam casos em que a desnormalização se justifica, há algumas situações em que a desnormalização traz um aumento no tempo de processamento, além de outras desvantagens. Nas demais seções deste artigo, tento lançar luz sobre diversas questões relacionadas à desnormalização.

 

Exemplo de tabela desnormalizada

Para exemplificar os argumentos das seções seguintes, vamos considerar a tabela denominada Locações, apresentada na Listagem 1. Essa tabela apresenta informações sobre carros de uma locadora de veículos, além de informações sobre os locatários desses carros e das locações realizadas.

 

Locações( placa_carro,

         licença_locatário,

         nome_locatário,

         endereço_locatário,

         modelo_carro,

         cnpj_fabricante_carro,

         razão_social_fabricante_carro,

         qtd_km_rodados_total,

         qtd_km_rodados_por_locatário,

         data_início_locação,

         data_término_locação )

Listagem 1.

 

Essa tabela está claramente desnormalizada. Para entender o porquê disso, considere a existência das seguintes dependências funcionais (entre outras):

 

·         cnpj_fabricante_carro à razão_social_fabricante_carro (dependência transitiva da chave primária; violação da 3FN)

·         licença_locatário à nome_locatário (dependência parcial da chave primária; violação da 2FN)

 

Em uma tabela desse tipo (desnormalizada) algumas de suas colunas (não componentes da chave) não dependerão única e exclusivamente da chave primária. De acordo com Fabian Pascal (veja Nota), essa característica leva a possíveis dependências:

 

1.      Uma ou mais colunas dependem somente de uma parte da chave (composta), o que corresponde à violação da 2FN.

2.      Dependência indireta da chave. Isto é, uma ou mais colunas dependem de outra coluna (não chave) que por sua vez depende da chave. Isso corresponde à violação da 2FN.

3.      Dependências multivaloradas dentro da chave da tabela. Isso corresponde à violação da 4FN.

4.      Dependências de junção dentro da chave primária, o que corresponde à violação da 5FN.

 

Nota

Fabian Pascal é um especialista em bancos de dados relacionais de opiniões um tanto polêmicas e declarações (um outro tanto) ríspidas, mas que merecem a atenção de qualquer profissional que trabalhe com SGBDs. Visite www.dbdebunkings.com.

Projeto lógico versus projeto físico

Um aspecto polêmico envolvendo a normalização e a desnormalização é a confusão entre os projetos lógico e físico de um banco de dados. A confusão típica é que “normalização degrada o desempenho do banco de dados”. Até especialistas da área de bancos de dados mundialmente conhecidos, como Joe Celko, têm opiniões controversas sobre o assunto (veja Nota).

 

Nota

"The reason for denormalization is performance." (Tradução: “A razão para desnormalização é o desempenho”). Essa frase aparece na página 44 do livro intitulado SQL for Smarties (Autor: Joe Celko; Editora: Morgan Kaufmann).

 

A normalização é uma atividade típica do projeto lógico de um banco de dados. Nessa etapa do projeto de um banco de dados, um modelo conceitual (e.g., o modelo de entidades e relacionamentos) é mapeado para um modelo lógico (e.g., o modelo relacional).

Já os aspectos relativos ao desempenho devem ser tratados na etapa posterior ao projeto lógico, denominada projeto físico do banco de dados. É no projeto físico que questões que influenciam no desempenho devem ser consideradas (como métodos de armazenamento e acesso, definições de índices sobre as tabelas envolvidas, características do hardware utilizado, a freqüência de execução das operações de consulta e de manipulação envolvidas, detalhes de implementação do SGBD sendo utilizado, grau de acesso concorrente a disco, etc.).

Mesmo sem considerar a confusão entre os projetos lógico e físico, a desnormalização ainda não garante um melhor desempenho sobre operações em um banco de dados, conforme veremos nas duas seções seguintes.

Consulta versus manipulação

Voltando à questão das junções levantada anteriormente e analisando o problema mais detalhadamente, podemos constatar que a sobrecarga de processamento necessária para manter a integridade dos dados (através da definição de gatilhos, por exemplo) pode não compensar o ganho de desempenho obtido com a desnormalização. De fato, a manutenção da integridade pode necessitar das mesmas operações de junção que a desnormalização se propunha a eliminar!

Para exemplificar, vamos considerar a tabela Locações. Para obter informações sobre locações, locatários e carros utilizados, a utilização dessa tabela desnormalizada realmente produz uma execução mais eficiente. Afinal de contas todos os dados necessários estão armazenados em uma única tabela, o que normalmente leva o SGBD a posicionar essas informações em blocos de disco contíguos.

No entanto, o que acontece quando um novo registro deve ser adicionado à tabela Locações? Uma das operações que devem ser realizadas para garantir que os dados permaneçam consistentes é atualizar a quantidade total de quilômetros rodados pelo carro (coluna qtd_km_rodados_total). Para isso, todos os valores dos campos de qtd_km_rodados_total deverão ser atualizados para os registros que possuírem valores de placa do carro e de licença do locatário semelhantes ao registro recém incluído. Deixo como exercício para o leitor verificar que há também problemas quando da exclusão e da atualização de registros nessa tabela. Todos esses problemas provenientes da sua desnormalização!

A conclusão é que, se por um lado a desnormalização tornou a obtenção de informações na tabela em questão mais eficiente, por outro lado o desempenho das operações de manipulação (inserção, remoção e atualização) fica comprometido. De uma forma geral, um dos aspectos que pesam contra a desnormalização é a questão da consistência (ou integridade) dos dados. Um benefício eventual obtido pela desnormalização (aumento do desempenho em uma determinada consulta de seleção) tem seu preço: uma tabela desnormalizada fica vulnerável ao surgimento de anomalias quando manipulações são realizadas sobre ela, e a integridade dos dados fica ameaçada.

Consulta versus consulta

A seção anterior descreve a polêmica mais óbvia em relação à escolha entre normalização e desnormalização (eficiência em consultas versus manutenção de integridade dos dados). No entanto, a sobrecarga necessária para manutenção da integridade dos dados não é o único fator a considerar quando o desenvolvedor estiver pensando em desnormalizar um esquema. Há um outro aspecto menos óbvio.

Considere novamente os dados da tabela Locações. Essa tabela armazena dados sobre três conceitos distintos: carros, locatários e as próprias locações. O que acontece quando aplicações de bancos de dados precisam obter acesso a esses dados separadamente?

Por exemplo, digamos que o departamento de marketing da locadora precisa enviar uma mala direta para as pessoas que já alugaram carros. Provavelmente, essa tarefa envolveria uma consulta sobre a tabela Locações, para resgatar somente os dados relativos a clientes (nome_locatário e endereço_locatário). Como esses dados estão em uma tabela que possui diversas outras colunas não relacionadas a clientes, a quantidade de informações sobre clientes por bloco de disco será menor do que se houvesse uma tabela armazenando somente dados sobre clientes. Conseqüentemente, o SGBD levará mais tempo para resgatar os dados necessários para montar a mala direta ao utilizar a tabela desnormalizada Locações. Perceba que esse ponto acaba indo de encontro ao citado anteriormente neste artigo. Ou seja, um dos benefícios da desnormalização (a redução na quantidade de junções) acaba sendo prejudicado em situações específicas.

De uma forma geral, se for tomada a decisão de aglutinar dados de duas ou mais tabelas em uma única tabela (ou seja, desnormalizar), as aplicações que necessitam ter acesso aos dados que do contrário estariam em uma tabela separada terão agora que ler desnecessariamente outras informações. E a leitura dessas outras informações desnecessárias aumenta o tempo de processamento das consultas de seleção.

Note que essa situação de diferentes aplicações acessarem diferentes informações é típica em um banco de dados corporativos. O fato é que a desnormalização que resultou na tabela Locações fez com que o seu esquema relacional ficasse apropriado para uma determinada aplicação (consultas envolvendo dados sobre locatários, carros e locações simultaneamente). Mas e as outras aplicações que acessam o mesmo banco de dados e têm necessidades de informações diferentes? A resposta é que o desempenho dessas aplicações fica prejudicado pelo fato delas terem que acessar dados que simplesmente não as interessam. Isso para não mencionar as necessidades de aplicações que sejam construídas no futuro.

Uma ação aconselhável é que o desenvolvedor estude as funcionalidades específicas do SGBD que irá utilizar (mais particularmente, as características relativas ao projeto físico de bancos de dados). Provavelmente, haverá alguma funcionalidade que aumente o desempenho de uma determinada consulta sem que seja necessário o uso da desnormalização.

 

A normalização também não é uma panacéia

Apesar de tudo que foi descrito nas seções anteriores, algumas vezes a violação das regras da normalização se faz útil. A idéia geral da normalização é que o desenvolvedor de um banco de dados aplique as regras das formas normais até “as últimas conseqüências”, ou seja, até a quinta forma normal. Entretanto, de acordo com Chris Date, está idéia não deve ser tomada como lei. Um banco de dados completamente normalizado muitas vezes contém tantos grupos de dados (domínios de valores) fragmentados que dificultam por demais a tarefa de obtenção de informações.

Qualquer um envolvido no desenvolvimento de um banco de dados deve ter conhecimento da técnica de normalização. No entanto, o desenvolvimento do banco não deve necessariamente se basear somente nessa técnica. Violar as regras da normalização de forma consciente e cuidadosa é aceitável, mas isso somente deve ser feito quando os benefícios da mudança justificam a violação da(s) regra(s). Abaixo cito dois casos, um em que a desnormalização pode ser evitada e outro em que ela pode ser aplicada.

 

·         Um primeiro exemplo é o caso das desnormalizações criadas por motivos históricos, e das desnormalizações criadas para geração de relatórios específicos. Nesse caso, a desnormalização pode ser evitada através do uso da desnormalização virtual. Uma desnormalização virtual é aquela criada através de visões (views). Ou seja, poder-se-iam construir diversas visões sobre a(s) tabela(s) desnormalizada(s), onde cada visão seria utilizada por uma determinada aplicação. No entanto, há que se considerar o outro lado da moeda (ou gume da faca!): o que um SGBD normalmente faz quando uma visão é solicitada é executar uma consulta – ou seja, de qualquer modo, a consulta sobre a tabela desnormalizada teria de ser executada. Por outro lado, há SGBDs que trabalham com a materialização de visões. Ou seja, os dados da visão ficam armazenados (o Oracle e o SQL Server 2000, por exemplo, possuem esse recurso). A vantagem é ter o resultado pré-computado quando for necessário; contudo, a visão será atualizada toda vez que ocorrerem alterações nos dados da(s) tabela(s) envolvidas.

·         Como um segundo exemplo, considere duas tabelas, Produtos e GrupoProdutos, uma que armazena produtos e outra que armazena grupos de produtos. Considere também que há um relacionamento um-para-muitos entre Produtos e GrupoProdutos. Considere mais ainda que o campo nome em GrupoProdutos é chave candidata. O diagrama da Figura 1 ilustra duas alternativas de projeto para essas duas tabelas. Na primeira alternativa, segue-se o projeto clássico. Já na segunda alternativa, o nome do grupo de produtos é adicionado à tabela Produtos, originando uma desnormalização nessa tabela. Além disso, esse campo agora é o utilizado como chave estrangeira. Note que o tamanho de cada um dos registros da tabela Produto aumenta em relação à primeira alternativa (pois o nome do produto é do tipo CHAR(20), que ocupa mais espaço que INTEGER), o que faz com que menos registros por bloco sejam trazidos do disco para a memória principal. Por outro lado, essa desnormalização faz com que a informação de nome do grupo de cada produto esteja presente em cada linha da tabela Produtos. Esse exemplo ilustra o fato de que algumas vezes a repetição de um campo em uma tabela (ou seja, desnormalização) pode economizar uma junção e causar efeitos colaterais negativos irrelevantes (nesse exemplo, o pouco esforço de atualização), sobretudo se o campo em questão for de pouca mutabilidade. Em particular, se a informação de nome do grupo de produtos for sempre necessária quando um produto for requisitado, talvez a segunda alternativa seja a melhor, pois isso elimina o custo de junção entre as duas tabelas.

 

image002.jpg

Figura 1.

 

Conclusões

Talvez a razão de haver uma guerra religiosa acerca da normalização e da desnormalização seja o fato de não haver uma resposta óbvia para a pergunta: quais são as conseqüências de utilizar uma tabela desnormalizada? A normalização e a desnormalização são estados antagônicos de um banco de dados que não podem ser conciliados facilmente.

Nesse artigo, descrevi situações em que o uso de uma tabela desnormalizada traz prejuízos em vez de ganhos para o desempenho geral de um esquema relacional. O fato é que há dois gumes da faca, tanto para a desnormalização, quanto para a normalização. Aconselho um estudo detalhado e pontual de todos os fatores envolvidos no projeto do banco de dados. Somente se todos esses fatores forem cuidadosamente estudados, e se todas as alternativas relativas ao projeto físico forem consideradas, é que o desenvolvedor terá em mãos as informações suficientes para optar por violar ou não as regras da normalização.

Por fim, o que posso dizer para resumir esse artigo é que a desnormalização não é um monstro que deve ser expulso para os confins do mundo dos bancos de dados. Mas, a cada vez que você desnormaliza seu banco de dados, você paga um preço, que pode ser em perda de flexibilidade e manutenibilidade, em perda de integridade dos dados, ou mesmo em perda de desempenho.

Para Saber Mais

Existe um vasto material sobre o assunto “normalização versus desnormalização” disponível na Web. Abaixo, cito alguns endereços que podem servir como fonte adicional de estudo sobre o assunto.

 

·         DENORMALIZATION AND THE RULES OF RECONSTRUCTION: http://www.tdan.com/i014ht04.htm

·         Responsible Denormalization
http://www.winnetmag.com/SQLServer/Article/ArticleID/9785/9785.html

·         DENORMALIZATION FOR PERFORMANCE - ET TU ACADEMIA? http://www.dbdebunk.citymax.com/page/page/622733.htm 

·         Denormalization, Database normalization and Performance - Hidden dangers: http://www.databasedesign-resource.com/denormalization.html

·         Pattern: Denormalization (http://www.objectarchitects.de/ObjectArchitects/orpatterns/Performance/Denormalization/)

·         Responsible Denormalization, Michelle A. Poolet, http://www.winnetmag.com/Articles/Index.cfm?ArticleID=9785