O banco quase sempre conta uma parte da história
Um relatório crítico levava 45 segundos. O execution plan mostrava table scan em 3 tabelas. O banco dava as pistas — o trabalho era montar o caminho completo.
O cenário
O relatório era usado pelo financeiro todos os dias, primeira hora. Cruzava dados de 3 tabelas com 500k+ registros cada. Com o crescimento de dados, o tempo subiu de "aceitável" para 45 segundos — o suficiente para o usuário achar que o sistema travou.
A investigação
O primeiro passo foi olhar o execution plan. Table scans nas 3 tabelas principais, nested loops onde deveria haver hash joins, e uma procedure cheia de cursores processando linha por linha o que poderia ser uma operação de conjunto.
O problema não era um — eram três camadas:
- Índices ausentes — as colunas de JOIN e WHERE não tinham índices compostos
- Cursores — processamento row-by-row em vez de set-based
- Sem paginação — o relatório carregava todos os registros de uma vez, mesmo que o usuário visse apenas 50 por página
A solução
Criei índices compostos cobrindo as colunas de JOIN + filtro mais frequentes. Reescrevi a procedure inteira: eliminei os cursores, substituí por CTEs e operações de conjunto. Adicionei paginação server-side com OFFSET/FETCH para entregar apenas a página solicitada.
O resultado
Tempo final: 2 segundos. A diferença não veio de uma única mudança, mas da combinação. O índice sozinho já teria ajudado, mas sem eliminar os cursores e sem paginar, o ganho seria parcial.
O aprendizado
O execution plan diz onde dói, mas não diz tudo. Ele mostra que tem table scan, mas não mostra que a procedure está iterando linha por linha. Otimizar banco é seguir pistas em camadas — e cada camada que você resolve revela a próxima.