9 problemas de performance em banco de dados e soluções práticas
Performance de banco de dados não é sorte: é diagnóstico e correção sistemática. Se você gerencia aplicações que dependem de consultas rápidas, conhece a sensação de ver uma query simples levar segundos, ou minutos. Abaixo, os 9 problemas mais comuns que encontro em ambientes reais e o que fazer em cada caso.
1. Queries sem índice adequado
O índice errado ou ausente força varreduras completas na tabela. Uma tabela de 1 milhão de linhas sem índice no campo de filtro pode demorar 10x mais. Solução: analise o plano de execução (EXPLAIN) e crie índices compostos para os filtros mais usados.
2. Locks e deadlocks em concorrência
Transações longas ou mal escritas geram locks que bloqueiam outras operações. Exemplo real: uma loja virtual travou por 30 segundos porque uma atualização de estoque segurava linhas sem COMMIT. Solução: reduza o tempo de transação e use isolamento READ COMMITTED.
3. Estatísticas desatualizadas
O otimizador de queries depende de estatísticas para escolher o plano. Sem atualização recente, ele pode optar por um índice ineficiente. Em um caso, uma tabela de logs com 5 milhões de registros tinha estatísticas de 3 meses atrás, a consulta passou de 200ms para 8s. Solução: programe coleta de estatísticas após cargas significativas.
4. Subqueries aninhadas sem necessidade
Subqueries dentro de WHERE ou SELECT podem ser reescritas como JOINs, reduzindo o número de varreduras. Uma query com 3 subqueries em um sistema de relatórios caía de 12s para 1,2s com JOIN. Solução: prefira JOINs e CTEs.
5. Hardware insuficiente para o volume
Disco HDD vs SSD, RAM abaixo do recomendado ou CPU saturada afetam diretamente. Um banco de 50GB em HDD com 4GB de RAM sofre com swap de disco. Solução: monitore uso de recursos e migre para SSD e RAM compatível com o working set.
6. Fragmentação de índices
Índices com alta fragmentação (acima de 30%) degradam a leitura sequencial. Um índice de 10MB fragmentado em 50% pode adicionar 40% de tempo de scan. Solução: reorganize ou reconstrua índices periodicamente.
7. Buffer pool mal dimensionado
No MySQL/InnoDB, o buffer pool define quantos dados ficam em memória. Se for pequeno, cada consulta lê do disco. Um cliente com 2GB de buffer pool para 20GB de dados ativos tinha 90% de cache miss. Solução: ajuste o buffer pool para 70-80% da RAM disponível.
8. SELECT * em produção
Trazer todas as colunas desperdiça I/O e memória. Uma tabela com 50 colunas usada em uma listagem de 3 campos consumia 5x mais dados. Solução: liste apenas as colunas necessárias.
9. Tabelas sem chave primária
Sem PK, o banco não tem referência única para indexação e replicação. Em um ambiente de e-commerce, uma tabela de pedidos sem PK gerava locks em toda a tabela a cada INSERT. Solução: adicione uma coluna auto-incremento como PK.
Não tente resolver todos de uma vez. Comece pelo plano de execução da query mais lenta: se faltar índice, crie; se houver lock, revise a transação. Ajuste um gargalo por semana e meça o tempo de resposta antes e depois.
Perguntas frequentes
Como identificar a query mais lenta no banco?
Ative o slow query log ou use ferramentas como Performance Schema (MySQL) ou pg_stat_statements (PostgreSQL). Ordene por tempo de execução.
Qual a diferença entre reorganizar e reconstruir índice?
Reorganizar desfragmenta sem bloquear a tabela por muito tempo; reconstruir cria um novo índice e pode exigir bloqueio. Reconstrua para fragmentação acima de 40%.
Vale a pena usar cache de query?
Depende. Em bancos com muitas leituras repetidas, sim. Em ambientes de escrita intensa, o cache pode adicionar overhead de invalidação.
O que causa deadlock?
Duas transações que bloqueiam recursos que a outra precisa. Exemplo: transação A bloqueia linha 1 e espera linha 2; B bloqueia linha 2 e espera linha 1.
Como dimensionar buffer pool?
Monitore o cache hit ratio. Se estiver abaixo de 95%, aumente o buffer pool até o limite da RAM disponível, sem comprometer o SO.
Devo usar índices em todas as colunas?
Não. Índices em excesso aumentam o custo de INSERT/UPDATE e ocupam espaço. Crie apenas para colunas usadas em WHERE, JOIN ou ORDER BY frequentes.