Quando um sistema fica lento, o banco de dados é quase sempre o primeiro suspeito — e frequentemente é culpado. Bancos de dados são pontos de centralização: todas as requisições eventualmente convergem para eles.
Este artigo explora os gargalos mais comuns em bancos de dados, como identificá-los, e estratégias para resolvê-los.
O banco de dados é o coração do sistema. Quando ele sofre, tudo sofre.
Por que Bancos São Gargalos Frequentes
Centralização
Enquanto aplicações escalam horizontalmente com facilidade, bancos de dados tradicionais são mais difíceis de escalar.
100 instâncias de aplicação
↓
1 banco de dados
↓
Gargalo
Estado compartilhado
Diferente de aplicações stateless, bancos mantêm estado que precisa ser consistente — isso limita paralelismo.
I/O intensivo
Bancos fazem muito I/O de disco, que é ordens de magnitude mais lento que operações em memória.
Tipos de Gargalos
1. Queries lentas
A causa mais comum. Uma única query mal escrita pode derrubar o sistema.
Sintomas:
- Latência alta em operações específicas
- CPU do banco alta
- Locks de longa duração
Causas:
- Falta de índices
- Índices não utilizados
- Full table scans
- Joins ineficientes
- N+1 queries
2. Conexões esgotadas
Pool de conexões no limite.
Sintomas:
- Timeouts de conexão
- Aplicação "trava" esperando conexão
- Conexões idle no banco
Causas:
- Pool subdimensionado
- Queries lentas segurando conexões
- Connection leaks
- Transações longas
3. Locks e contenção
Transações disputando os mesmos dados.
Sintomas:
- Latência errática
- Deadlocks
- Transações abortadas
Causas:
- Transações longas
- Updates em linhas "quentes"
- Falta de índices causando locks de tabela
4. I/O de disco
Disco não consegue acompanhar a demanda.
Sintomas:
- I/O wait alto
- Queries lentas mesmo com bons índices
- Buffer pool thrashing
Causas:
- Dados maiores que memória disponível
- Muitas escritas
- Disco subdimensionado
5. Replicação atrasada
Réplicas não acompanham o primário.
Sintomas:
- Lag de replicação crescente
- Leituras inconsistentes
- Réplicas caindo
Causas:
- Volume de escrita muito alto
- Rede lenta entre primário e réplica
- Réplica subdimensionada
Identificando o Gargalo
Métricas essenciais
| Métrica | O que indica |
|---|---|
| Query time (p95, p99) | Queries lentas |
| Connections used/available | Pressão de conexão |
| Lock wait time | Contenção |
| Buffer hit ratio | Eficiência de cache |
| Disk I/O wait | Pressão de disco |
| Replication lag | Atraso de réplicas |
Ferramentas
PostgreSQL:
pg_stat_statements— queries mais lentaspg_stat_activity— conexões ativaspg_locks— locks ativosEXPLAIN ANALYZE— plano de execução
MySQL:
slow_query_log— queries lentasSHOW PROCESSLIST— conexões ativasSHOW ENGINE INNODB STATUS— estado internoEXPLAIN— plano de execução
A query mais importante
-- PostgreSQL: Top queries por tempo total
SELECT query, calls, total_time, mean_time
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 20;
O impacto de uma query = frequência × tempo médio.
Soluções
Para queries lentas
1. Adicione índices
-- Antes: full table scan
SELECT * FROM orders WHERE customer_id = 123;
-- Depois: index scan
CREATE INDEX idx_orders_customer ON orders(customer_id);
2. Reescreva queries
-- Antes: N+1
for order in orders:
customer = query("SELECT * FROM customers WHERE id = ?", order.customer_id)
-- Depois: JOIN
SELECT o.*, c.*
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.date > '2024-01-01';
3. Desnormalize quando necessário
Adicione dados redundantes para evitar joins caros.
4. Particione tabelas grandes
CREATE TABLE orders (
id SERIAL,
created_at TIMESTAMP,
...
) PARTITION BY RANGE (created_at);
Para conexões
1. Dimensione o pool corretamente
connections = (cores * 2) + spindles
Onde spindles = número de discos.
2. Use connection poolers
- PgBouncer (PostgreSQL)
- ProxySQL (MySQL)
3. Encontre e corrija leaks
Monitore conexões que não retornam ao pool.
Para contenção
1. Transações curtas
-- Evite
BEGIN;
-- ... operações demoradas ...
-- ... processamento em código ...
COMMIT;
-- Prefira
-- Processe dados antes
BEGIN;
-- Apenas operações de banco, rápidas
COMMIT;
2. Ordene acessos
Sempre acesse tabelas/linhas na mesma ordem para evitar deadlocks.
3. Use SKIP LOCKED para filas
SELECT * FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED;
Para I/O
1. Aumente memória (buffer pool)
Mais dados em cache = menos I/O.
2. Use SSDs
Diferença de 100x em latência comparado a HDDs.
3. Separe dados por temperatura
Dados quentes em storage rápido, dados frios em storage barato.
Para escala
1. Read replicas
Distribua leituras entre réplicas.
Escrita → Primário
Leitura → Réplicas (load balanced)
2. Sharding
Divida dados entre múltiplos bancos.
customer_id % 4 = shard_number
3. CQRS
Separe modelo de leitura do modelo de escrita.
Boas Práticas
Desenvolvimento
- Sempre use EXPLAIN antes de ir para produção
- **Evite SELECT *** — busque apenas colunas necessárias
- Pagine resultados — LIMIT e OFFSET ou cursor
- Use prepared statements — cache de planos de execução
- Monitore N+1 — ORM pode esconder queries ruins
Operação
- Monitore continuamente — queries que eram rápidas podem ficar lentas
- Mantenha estatísticas atualizadas — ANALYZE regularmente
- Planeje manutenção — VACUUM, reindex, etc.
- Teste com dados realistas — produção tem mais dados que dev
- Tenha alertas de slow query — seja proativo
Conclusão
Bancos de dados são frequentemente gargalos porque:
- Centralizam acesso a dados
- São difíceis de escalar horizontalmente
- Dependem de I/O, que é lento
Para evitar problemas:
- Escreva queries eficientes — índices, joins otimizados
- Dimensione recursos adequadamente — conexões, memória
- Monitore proativamente — encontre problemas antes dos usuários
- Planeje para escala — réplicas, sharding, cache
Trate seu banco de dados como um recurso precioso. Cada query tem um custo.