Práticas de otimização em Banco de Dados

Práticas de otimização em Banco de DadosSaudações, leitores!
Bom, já sabemos que uma aplicação não é feita somente de códigos-fonte bem estruturados. Devemos também nos importar com as instruções SQL que executamos no banco de dados, evitando o consumo desnecessário de processamento. De nada adianta ter uma aplicação com visual elegante e boas funcionalidades se houver lentidão para gravar ou consultar informações no banco de dados.

 

A princípio, o título deste artigo seria a sexta parte sobre dicas para o desenvolvimento de software, mas como se trata de um assunto especialmente voltado a banco de dados, decidi não mesclar os temas.
Em resumo, um banco de dados é uma coleção de informações de forma estruturada com a finalidade de armazenar dados sobre um determinado assunto. Como desenvolvedores, devemos manipular esses dados de forma otimizada para colaborar com o desempenho da aplicação.

Para uma melhor compreensão, vamos fazer uma breve analogia. Imagine que você precise encontrar o número do telefone de uma empresa na lista telefônica.
Quanto tempo você leva para encontrar este número na lista?
Quanto maior for a lista, mais tempo você levará para encontrá-lo, não é?
Bom, mas podemos utilizar algumas “práticas” para encontrá-lo mais rápido. Por exemplo, abrir a lista direto nas páginas comerciais e depois encontrar a letra inicial do nome da empresa. Já facilita bastante!

Pois bem, agora imagine o banco de dados como uma lista telefônica. Da mesma forma que você se esforça para encontrar um número, o motor (engine) do banco de dados também faz esse trabalho, é claro, de forma automática. No entanto, assim como na lista telefônica, no banco de dados também precisamos de práticas para agilizar a busca de dados nas tabelas.
Bom, considerando que você, leitor, já detém conhecimento em linguagem SQL, esse artigo aborda basicamente algumas dessas “práticas”, especificamente voltadas para ambientes onde há conexões simultâneas no banco de dados, ou seja, softwares de médio a grande porte. Logo, a maioria delas não surtem grandes diferenças em softwares de pequeno porte que possuem quantidade relativamente pequena de dados. Na verdade, são pequenos “ajustes” que, quando feitos em massa, podem trazer melhores performances nas respostas de consultas SQL. Caso você tenha alguma sugestão, não hesite em deixar um comentário, ok?


Evite o asterisco

Em uma instrução SELECT, evite adicionar o asterisco para selecionar todas as colunas. Quando você o usa, o banco de dados precisa interpretar o asterisco como a junção de todas as colunas da tabela, realizando uma espécie de conversão. Além do mais, dificilmente é preciso selecionar TODAS as colunas de uma tabela, ao menos que seja realmente necessário.

-- ao invés de
SELECT * FROM PRODUTOS
 
-- escreva
SELECT Codigo, Descricao, Preco FROM PRODUTOS

 

Cuidado com o DISTINCT
O comando DISTINCT tem a função de apresentar as ocorrências distintas de um campo em todos registros selecionados na tabela. A questão é que o banco de dados precisa realizar uma série de operações para aplicar um DISTINCT:

  • Selecionar os registros
  • Comparar os registros que tem valores iguais e ignorá-los
  • Comparar os registros que tem valores diferentes e selecioná-los
  • Construir o resultado utilizando as comparações acima

Em uma tabela com muitos registros, o DISTINCT pode exigir grande processamento do banco de dados. Quando for utilizá-lo, certifique-se de adicionar condições na cláusula where para que o processamento seja realizado no mínimo de registros possível.


A desvantagem do ORDER BY

A ordenação de registros de uma tabela é um recurso importante, mas também deve ser utilizada com cautela. Considere uma tabela com milhares de registros e a seguinte instrução SQL:

SELECT Codigo, Nome, Cidade FROM CLIENTES ORDER BY Nome

Ao rodar essa instrução, o banco de dados terá que ler todos os nomes dos clientes da tabela e classificá-los em ordem alfabética para exibi-los no resultado. Essa ordenação pode demorar um bom tempo e afetar o desempenho do banco de dados.
A sugestão é efetuar essa ordenação na aplicação cliente em um conjunto de dados. No Delphi, pode-se utilizar a propriedade IndexFieldNames de um Dataset para realizar a ordenação em memória. Em uma arquitetura do tipo Cliente/Servidor, ordenar os registros na aplicação cliente pode reduzir a carga de processamento no banco de dados.


Joins em excesso
Muitas vezes precisamos fazer ligações entre tabelas em uma instrução SQL para obter valores relacionados, normalmente realizadas através de chaves estrangeiras utilizando a cláusula Join. Porém, Joins em excesso podem comprometer o desempenho da consulta, uma vez que o banco de dados irá traçar todo um plano de execução para associar todas as tabelas. Para cada Join, o banco de dados precisa “armazenar” a informação das chaves do relacionamento para garantir o retorno esperado.
Já encontrei casos onde era praticamente inviável obter um valor de uma tabela por causa do longo plano de Joins que a instrução percorria até encontrá-lo. A solução foi criar uma tabela adicional para reduzir a “distância” entre as tabelas, e, consequentemente, reduzir a quantidade de Joins.


Valores totais devem ser gravados ou calculados?

Esse assunto é bastante discutido entre desenvolvedores e DBAs. Em uma tela de venda, imagine que para adicionar um item da venda é preciso digitar a quantidade e o preço unitário. Por exemplo:

Quantidade: 5
Preço unitário: 20,00
Total: 100,00

E então surge a questão: O valor total de 100,00 deve ser gravado em uma coluna na tabela ou gravar somente a quantidade e o preço unitário já é o suficiente para calcular o total?
Bom, para gravar o valor total na tabela é necessário criar mais um campo, no entanto, facilitará consultas SQL, já que será necessário apenas incluir essa coluna na instrução SELECT.

SELECT NumVenda, CodProduto, PrecoUnitario, Qtde, Total FROM ITENS_VENDA

Se o desenvolvedor optar por gravar somente a quantidade e o preço unitário, deve estar ciente de que, ao consultar vários itens, a multiplicação da quantidade pelo preço para calcular o total será feita para cada registro selecionado, e em seguida, para calcular o total da venda será necessário somar todos os valores totais dos itens.

SELECT NumVenda, CodProduto, PrecoUnitario, Qtde,
       PrecoUnitario * Qtde AS Total FROM ITENS_VENDA

Essa questão é relativa para cada tipo de sistema. Cabe ao responsável pelo desenvolvimento analisar a melhor opção para o projeto.

 

Embora sejam dicas rápidas, procure adquirir o hábito de praticá-las quando trabalhar com banco de dados, ok?
Fico por aqui, leitores:

UPDATE AndreCelestino.com SET STATUS = 'Fim' WHERE ID_Artigo = 38

Haha! Até a próxima!


 

Compartilhe!
Share on FacebookTweet about this on TwitterShare on LinkedInShare on Google+Pin on PinterestEmail this to someone

21 comentários

  1. Gostei ! Sempre tomei este cuidado que as consultas ficam super rapidas que o cliente elogia! Mas estou tendo um problema com o Finearest na consulta qdo o sistema funcina em rede. Att, Marcelo.

  2. Ótimo artigo. Utilizo a maioria dessas otimizações.

    Já tive que recorrer de gravar o total da venda, quando a mesma for finalizada, pois o sistema começou a ficar muito lento em consultas.

    1. Olá, Chagas! Depende do tipo de informação que você quer exibir, ou seja, se for um conjunto de dados oriundo de duas ou mais tabelas (joins) e o intuito é só exibí-los para o usuário de forma estática, recomendo utilizar Views. Por outro lado, se você for realizar algum processamento nos dados antes de exibí-los (como concatenações, formatações e cálculos), então eu sugiro que utilize Stored Procedures!

  3. Artigo muito bom, temos um dificuldade com um sistema legado em nossa empresa com relação a performance é observando o profiler do banco observei muitos dos pontos que você abordou. Parabéns.

    1. Olá, jhon. Na verdade faltou bastante coisa nesse artigo, e a criação de índices é uma delas, rsrs. Tive que selecionar um conjunto pequeno de tópicos para não deixar o artigo muito extenso. Obrigado pelo comentário!

  4. amigo,valeu ate demais, o que meu professor demorou para me ensinar em um semestre, voce fez em poucos minutos…
    Obrigado

  5. Mais uma vez gostei das dicas, eu tenho clientes que a base de dados em firebird chega a mais de 1GB, mais de 10 anos de uso, a melhor forma de melhorar a performance foi criar tabelas temporárias dentro do db e alimentar elas via strore procedures tipo ‘insert into vendas_tmp select * from vendas where ‘parâmetros da seleção’ depois faço todas as outras operações na tabela temporária, isso resultou em ganho de performance tipo 20x mais rapido.
    Mas acho que se usássemos views seria uma alternativa, o que pode me dizer?

    1. Olá, Gerson!
      Views são adequadas para trazer dados de diferentes tabelas, formando uma visão de dados personalizada, evitando, assim, vários inner joins a cada consulta. Para melhorar o desempenho em tabelas que contém uma grande massa de dados, sugiro trabalhar com índices (indexes) para manter os dados devidamente ordenados, agilizando as buscas. Clique aqui para conferir um tutorial interessante sobre indexação.

      Abraço!

  6. Ola, André.
    Gostei do artigo que você recomendou.
    Li, entendi o conceito, e eu uso parcialmente a recomendação, mas em algumas matérias que li anteriormente foi recomendado evitar o uso de índices com vários campos para não comprometer a performance. Mesmo assim, desde a criação do meu DB, montei índices com varios campos tipo “cliente+data+numero_do_pedido” e um índice individual para cada tipo de consulta.
    Usava FindNearest em tabelas mas mudei para Locate em SQL. Melhorou, mas não fiquei satisfeito. Por isso, criei tabelas temporárias dentro do DB e as alimento com procedures para pré-sseleção e depois uso SQL para completar a seleção e visualizar. Em alguns casos a pré-seleção pode envolver mais de 200.000 registros de um total de 2.340.138, como todos os itens vendidos em um ano. Depois posso somar só um produto ou as vendas para um determinado cliente e outros.
    Direto no servidor é menos de 1 segundo. Ótimo. Dependendo do terminal, mais de 1 minuto. Antes, ao usar tabelas e Filter, dava par ir buscar um café. Mesmo assim, como são varias consultas seguidas e acrescentando as outras operações requisitadas pelos terminais às vezes parece lento.

    1. Legal, Gerson. A técnica que você empregou para melhorar a performance das consultas é bem válida. Como desenvolvedores, sempre somos orientados a utilizar práticas de otimização (como tabelas temporárias, views, índices, triggers, cache e pequenas projeções) para aprimorar a experiência do usuário com a aplicação. Atualmente, com os conceitos de Business Intelligence, OLAP, Big Data e Data Mining, essas práticas se tornaram primordiais, ou, talvez, obrigatórias!

  7. OK, Obrigado. Vou pesquisar, não conheço estes conceitos ainda. Todos os dias tem coisas novas, só com a colaboração do conhecimento como você tem feito, e eu gostaria de colaborar também, independente da concorrência acirrada, crescemos e tenho certeza que quanto mais aprendemos e compartilhamos nossa experiência e conhecimento, melhoramos.
    Sucesso para todos nós.

  8. Boa Tarde André,

    No seus projetos em delphi você usa algum ORM ou você trabalha com query ?

    att…

    1. Boa noite, Francisco, tudo bem?
      Já trabalhei com as duas abordagens. A escolha por uma delas é uma particularidade de cada projeto. Mesmo assim, eu sugiro que, para projetos grandes, seja utilizado um ORM para facilitar as manutenções corretivas e evolutivas. Para projetos pequenos, esse recurso talvez não seja necessário.

      Abraço!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Preencha o campo abaixo * Time limit is exhausted. Please reload CAPTCHA.