Pular para o conteudo principal

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

Andrews Ribeiro

Founder & Engineer

O problema

N+1 query costuma nascer de um código que parece limpo.

Algo como:

  1. buscar uma lista de pedidos
  2. para cada pedido, buscar o cliente
  3. 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:

  1. buscar pedidos com cliente já incluído
  2. buscar todos os customerId distintos e carregar em lote
  3. 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:

  1. reconhecer o padrão
  2. explicar por que ele escala mal
  3. sugerir correções compatíveis com o caso
  4. 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

Checklist de pratica

Use isto ao responder

Você concluiu este artigo

Próximo artigo Normalização vs pragmatismo: quando cada um Artigo anterior Como modelar entidades

Continue explorando

Artigos relacionados