Otimização de Desempenho¶
Quase toda "lentidão de banco" que chega para nós não é lentidão de banco: é uma consulta sem índice, um ORM emitindo trezentas consultas por requisição, ou um conjunto de dados que deixou de caber em memória. Trocar o servidor por um maior mascara o problema por alguns meses e devolve a mesma conta depois.
O método é sempre o mesmo: medir, encontrar as poucas consultas que respondem pela maior parte do tempo, corrigir a causa, medir de novo.
A ordem importa: corrigir uma camada torna a de baixo mais barata. O caminho inverso não funciona.
1. Medir antes de tocar¶
Sem número, otimização é palpite.
| Fonte | O que revela |
|---|---|
pg_stat_statements (PostgreSQL) | Consultas por tempo total acumulado — o ranking que importa |
performance_schema / slow query log (MySQL) | Consultas lentas e as executadas muitas vezes |
| Métricas de sistema | CPU, latência de I/O, memória, conexões ativas |
| Métricas de replicação | Lag, que denuncia escrita pesada ou réplica subdimensionada |
| Rastreamento na aplicação | Onde a requisição gasta tempo: banco, rede, ou o próprio código |
Tempo total vence tempo individual
Uma consulta de 2 segundos executada 10 vezes por dia importa menos que uma de 20 ms executada 200 mil vezes. Ordene por tempo total acumulado, não pela consulta mais lenta.
2. Aplicação: o problema mais barato de corrigir¶
- N+1 — uma consulta para listar, mais uma por item. É o padrão mais comum e o de maior ganho: resolve-se com
JOINou carregamento em lote. SELECT *— traz colunas que ninguém usa, impede índice de cobertura e infla a rede.- Paginação por
OFFSETalto —OFFSET 100000faz o banco varrer e descartar 100 mil linhas. Paginação por chave (keyset) não tem esse custo. - Ausência de lote — mil
INSERTindividuais custam muito mais que umINSERTcom mil linhas. - Transação longa — segura recursos, atrasa limpeza de versões antigas e aumenta o lag das réplicas.
3. Plano de execução¶
Ler o plano é o que separa correção de tentativa.
-- PostgreSQL: com tempo real e páginas lidas, não só a estimativa
EXPLAIN (ANALYZE, BUFFERS) SELECT ... ;
-- MySQL
EXPLAIN ANALYZE SELECT ... ;
O que procurar:
- Varredura sequencial em tabela grande — falta índice, ou o índice existe e não pode ser usado (função sobre a coluna, tipo incompatível,
LIKE '%algo'). - Estimativa muito distante do real — estatísticas desatualizadas. Rode
ANALYZEantes de concluir qualquer outra coisa. - Ordenação em disco — memória de trabalho insuficiente para o
ORDER BY/GROUP BY. - Junção pelo método errado — quase sempre consequência de estimativa ruim, não do planejador.
4. Índices¶
Índice bom resolve; índice demais custa em toda escrita.
- Índice composto na ordem certa — colunas de igualdade primeiro, faixa por último. Um índice em
(cliente_id, criado_em)serve a consulta por cliente e por cliente + período; o inverso não. - Índice de cobertura — quando o índice contém todas as colunas da consulta, o banco nem toca a tabela.
- Índice parcial — em PostgreSQL, indexar só as linhas que importam (
WHERE status = 'ativo') reduz tamanho e custo de manutenção. - Índices não usados são prejuízo — ocupam disco, tornam cada
INSERTmais caro e ainda inflam o backup. As estatísticas de uso do banco mostram quais nunca foram lidos. - Índice duplicado —
(a)é redundante quando existe(a, b).
5. Memória e cache¶
| Banco | Parâmetro central | Regra de partida |
|---|---|---|
| MySQL / InnoDB | innodb_buffer_pool_size | ~60–70% da RAM em servidor dedicado |
| PostgreSQL | shared_buffers + cache do SO | ~25% em shared_buffers, contando com o cache do sistema |
| PostgreSQL | work_mem | Por operação de ordenação — cuidado: multiplica por conexão |
| ClickHouse | Limite de memória por consulta | Evita que uma agregação derrube o nó |
O sinal a acompanhar é a taxa de acerto do cache. Quando ela cai de forma sustentada, o conjunto ativo cresceu além da RAM — e o próximo passo é memória ou particionamento, não mais índice.
6. Conexões¶
Banco de dados não gosta de milhares de conexões. Cada uma consome memória e disputa CPU.
- Pool obrigatório — PgBouncer no PostgreSQL, ProxySQL no MySQL, ou o pool do próprio framework, bem configurado.
- Menos conexões, mais vazão — acima de certo ponto, aumentar o pool piora o desempenho: a fila migra do pool para dentro do banco, onde é mais cara.
- Separar leitura de escrita — leitura direcionada às réplicas alivia o primário e é o caminho mais direto para escalar leitura.
7. Storage e I/O¶
O que sobra depois de tudo é físico:
- latência de escrita do volume, especialmente para o log de transação;
- IOPS disponível contra o exigido no pico;
- separação entre volume de dados e volume de log;
- compressão, que troca CPU por I/O — quase sempre um bom negócio em carga analítica.
Ganhos típicos¶
| Correção | Ganho observado |
|---|---|
| Índice ausente em consulta de alta frequência | 10× a 1000× na consulta |
| Eliminação de N+1 | 5× a 50× na latência da requisição |
buffer pool dimensionado para o conjunto ativo | 2× a 20× em carga de leitura |
| Pool de conexões corrigido | Fim dos picos de erro e latência mais estável |
| Leitura direcionada a réplica | Alívio proporcional ao volume de leitura |
Servidor maior é a última alternativa, não a primeira
Aumentar hardware antes de olhar plano de execução resolve por um tempo e cobra depois — com uma conta maior e o mesmo problema.
Páginas Relacionadas¶
- Administração — Revisão periódica de consultas lentas
- Replicação — Escalar leitura sem sobrecarregar o primário
- MySQL Multi-Região · PostgreSQL Multi-Região — Quando a latência é de rede, não de banco
- Alta Disponibilidade ClickHouse — Carga analítica separada do OLTP
- Instalação — Dimensionamento que evita metade destes problemas