7 de Fevereiro de 2025
N+1 query: como perceber e como resolver
Como reconhecer o padrão de N+1 em ORMs e consultas encadeadas, por que ele escala mal e quais correções realmente fazem diferença.
Andrews Ribeiro
Founder & Engineer
6 min Intermediario Sistemas
O problema
N+1 query costuma nascer de um código que parece limpo.
Algo como:
- buscar uma lista de pedidos
- para cada pedido, buscar o cliente
- para cada pedido, buscar os itens
Com poucos dados, passa despercebido.
O endpoint responde.
O desenvolvedor olha e pensa:
- “funcionou”
Só que funcionou do jeito mais caro possível.
Porque o sistema fez:
- 1 query para a lista
- mais 1 para cada item
E às vezes mais outra para cada relação adicional.
Então o problema não é só “tem muitas queries”.
O problema é que o custo cresce de forma boba à medida que a lista cresce.
Modelo mental
Pense assim:
N+1 query é uma forma de trabalho repetido que o sistema poderia ter resolvido em lote, mas resolveu item por item.
Essa frase é importante porque tira o tema do campo do “bug misterioso de ORM”.
No fundo, é um problema de acesso a dados.
Você já tinha informação suficiente para buscar junto ou em lote.
Mas o sistema escolheu:
- fazer a primeira busca
- depois repetir nova ida ao banco para cada item
Ou seja:
N+1 é menos sobre tecnologia específica e mais sobre padrão ruim de acesso.
Quebrando o problema
O nome ajuda a enxergar o padrão
Se você busca 1 lista de pedidos e depois faz 100 buscas de cliente, você fez:
- 1 + 100
Daí o nome.
Mas o que importa não é decorar a fórmula.
O que importa é perceber a cara do problema:
- primeiro vem uma coleção
- depois vem consulta repetida baseada em cada item da coleção
Quando isso acontece, o alerta deve subir.
Em volume pequeno, ele engana fácil
Esse é o motivo de passar tanto para produção.
Com 5 registros:
- parece rápido
- parece simples
- parece legível
Com 500:
- aumenta ida ao banco
- aumenta tempo total
- aumenta contenção
- aumenta chance de gargalo
Ou seja:
é o tipo de problema que se esconde bem em ambiente pequeno.
ORM ajuda a criar, mas não é o culpado único
Muita gente aprende N+1 como “problema de ORM”.
Não está totalmente errado.
ORM realmente facilita cair nisso porque deixa fácil navegar relações como se tudo já estivesse em memória.
Mas o problema não depende de ORM.
Você pode criar N+1 com:
- ORM
- query builder
- SQL manual
- chamadas repetidas para repositório
O ponto central continua sendo o mesmo:
o sistema resolveu em loop algo que deveria resolver em lote.
Eager loading é comum, mas não é resposta universal
A correção mais famosa é:
- eager loading
Em muitos casos, ela resolve bem.
Por exemplo:
- buscar pedidos já com cliente
- buscar lista já com itens relacionados
Mas nem sempre isso basta.
Às vezes o correto é:
- fazer join explícito
- buscar IDs e depois carregar em lote
- projetar só os campos necessários
- reorganizar o fluxo para não pedir dado demais
Se você só decorar “usa eager loading”, sua resposta fica curta demais.
O ganho real vem de reduzir ida repetida ao banco sem começar a puxar metade do schema por reflexo.
Às vezes o problema vira outra coisa se corrigido sem critério
Também dá para exagerar do outro lado.
Você detecta N+1, coloca eager loading em tudo, e de repente:
- puxa dado demais
- monta objeto gigante demais
- aumenta memória
- deixa query mais pesada do que precisava
Então corrigir N+1 não significa “carregar todas as relações sempre”.
Significa alinhar carregamento com o que aquela leitura realmente precisa.
Logs e profiling ajudam a perceber cedo
N+1 raramente aparece só olhando tela.
Ele costuma aparecer em sinais como:
- mesma query repetida várias vezes
- mesma tabela sendo acessada em loop
- tempo subindo junto com tamanho da lista
- endpoint lento sem lógica pesada aparente
Em time maduro, esse tipo de coisa aparece em:
- logs de query
- profiling do ORM
- APM
- revisão de código com atenção ao padrão de acesso
Quando ninguém olha isso, N+1 costuma sobreviver porque cada request isolada “parece pequena o bastante”.
Exemplo simples
Imagine uma página administrativa que lista pedidos recentes.
Você busca:
const orders = await orderRepository.findRecent()
Depois, para renderizar, faz algo equivalente a:
for (const order of orders) {
const customer = await customerRepository.findById(order.customerId)
}
Se vierem 50 pedidos:
- 1 query para os pedidos
- 50 queries para clientes
Se além disso cada pedido carrega itens separadamente:
- mais 50
Agora você tem 101 queries para montar uma tela que poderia ser resolvida de modo muito mais econômico.
Correções possíveis, dependendo do caso:
- buscar pedidos com cliente já incluído
- buscar todos os
customerIddistintos e carregar em lote - usar join ou projeção específica para aquela tela
A melhor escolha depende do uso real.
Mas todas compartilham a mesma ideia:
- parar de consultar item por item.
Erros comuns
Achar que N+1 é só detalhe de performance
Em escala, ele vira problema real de latência e carga.
Corrigir no escuro com eager loading em tudo
Isso pode trocar um problema por outro.
Não perceber que o custo cresce com o tamanho da lista
Esse é o comportamento típico do padrão.
Culpar só o ORM
O ORM pode facilitar, mas o desenho do acesso ainda é responsabilidade do time.
Medir só tempo final e não a quantidade de queries
Às vezes o endpoint ainda “parece aceitável”, mas a multiplicação de queries já está cobrando caro no banco.
Como um senior pensa
Um senior costuma olhar para esse tipo de fluxo e perguntar:
- essa relação está sendo carregada em loop?
- isso precisa vir junto?
- dá para buscar em lote?
- estou pedindo exatamente o dado que a tela precisa?
- essa correção vai resolver o padrão ou só esconder o sintoma?
Esse jeito de pensar importa porque evita duas respostas ruins:
- ignorar o N+1
- corrigir com carregamento exagerado
Maturidade aqui é perceber que acesso a dados é parte do design do sistema, não detalhe de implementação.
O que o entrevistador quer ver
Quando o tema aparece em entrevista, o avaliador geralmente quer ver se você consegue:
- reconhecer o padrão
- explicar por que ele escala mal
- sugerir correções compatíveis com o caso
- falar de medição e impacto real
Uma resposta forte costuma soar assim:
- “isso parece N+1 porque a lista é carregada primeiro e depois cada relação é consultada separadamente; eu avaliaria eager loading, join ou batch load dependendo do dado necessário e mediria quantidade de queries e latência”
Essa resposta é muito melhor do que só dizer:
- “eu colocaria include”
Porque ela mostra entendimento do padrão, não só memória de ferramenta.
N+1 query é o sistema fazendo em loop o que deveria fazer em lote.
Boa correção não é carregar tudo. É carregar certo.
Resumo rápido
O que vale manter na cabeça
- N+1 query aparece quando o sistema busca uma lista e depois abre uma nova consulta para cada item da lista.
- O problema não é só quantidade de queries; é multiplicar latência, carga no banco e trabalho repetido sem necessidade.
- Eager loading ajuda em muitos casos, mas nem sempre é a resposta completa; às vezes precisa mudar query, projeção ou fluxo.
- Em entrevista, resposta forte mostra como detectar o padrão, medir impacto e escolher a correção certa para o acesso real.
Checklist de pratica
Use isto ao responder
- Consigo explicar N+1 sem depender de jargão de ORM?
- Sei dizer por que algo que funciona com 5 registros quebra com 500?
- Consigo listar pelo menos três formas plausíveis de corrigir o problema?
- Sei diferenciar quando é N+1 de quando é outra causa de lentidão?
Você concluiu este artigo
Compartilhar esta página
Copie o link manualmente no campo abaixo.