---
term: 'O Problema de Consultas N+1'
seoTitle: 'O Problema de Consultas N+1: Causas, Detecção e Soluções'
headline: 'O que é o problema de consultas N+1?'
slug: problema-n-mais-1
category: api-realtime
shortDefinition: 'O problema de consultas N+1 é um padrão em que buscar N registros dispara uma query extra por registro — N+1 idas ao banco em vez de uma ou duas.'
relatedTerms:
  - relational-queries-document-databases
  - graphql-vs-rest
  - overfetching-underfetching
  - api-payload-optimization
  - database-index
contrastsWith:
  - overfetching-underfetching
faq:
  - question: 'O que é o problema de consultas N+1, em termos simples?'
    answer: 'Você busca uma lista de N registros com uma query, e aí o seu código faz mais uma query por registro para os dados relacionados — cem posts viram cento e uma queries. Cada query é individualmente rápida, e é exatamente por isso que o problema se esconde: nada é lento o bastante para alarmar alguém, até a página fazer centenas de idas ao banco.'
  - question: 'O que causa queries N+1?'
    answer: 'Lazy loading como padrão. ORMs e SDKs deixam você navegar relações como propriedades de objeto — post.author — e rodam uma query de forma transparente quando você toca numa delas. Coloque esse acesso dentro de um loop sobre N resultados e você escreveu N queries em silêncio. A abstração que tornou o acesso a dados agradável também tornou as idas ao banco invisíveis.'
  - question: 'Como corrigir o problema N+1?'
    answer: 'Quatro saídas padrão, escolhidas caso a caso: eager loading — avisar à query, de antemão, para incluir a relação; um join que busca os dois numa instrução só; batching — coletar as N chaves estrangeiras e buscá-las numa única query de contido-em; e loaders com escopo de requisição, que agrupam automaticamente. As quatro transformam N+1 idas ao banco em uma ou duas.'
  - question: 'Quão mais lento o N+1 é de verdade?'
    answer: 'Faça a conta: cada ida ao banco custa de um a cinco milissegundos antes de a query sequer rodar, então 100 linhas a 5 ms somam meio segundo, contra uma query em batch de ~10 ms. Correções publicadas do mundo real relatam páginas indo de cerca de 1,4 segundo para 0,16, e endpoints de API acelerando trinta vezes. A matemática piora linearmente com o tamanho da página — e catastroficamente sobre a rede.'
  - question: 'Por que o problema N+1 é tão comum em GraphQL?'
    answer: 'Porque resolvers rodam por campo, por objeto. Uma query de posts com seus autores roda o resolver de posts uma vez e o resolver de autor N vezes — o loop de ORM renascido no servidor. A cura canônica é o padrão loader: um agrupador com escopo de requisição que coleta os IDs de autor durante a execução e dispara uma busca em batch, memoizada para a requisição.'
  - question: 'Como detectar queries N+1?'
    answer: 'Procure muitas queries idênticas e rápidas, não uma lenta — essa é a assinatura. O log de debug do ORM mostra a mesma instrução repetida com parâmetros diferentes; logs de queries lentas erram o alvo por completo, porque cada query é rápida; ferramentas de APM sinalizam o padrão de spans explicitamente. O hábito que pega cedo: leia o log de queries de uma renderização de página, e conte.'
  - question: 'O problema N+1 acontece em bancos de documentos e APIs REST?'
    answer: 'Em todo lugar onde dados têm relações. Em bancos de documentos, é um find por documento referenciado — resolvido com mecanismos de include, queries de contido-em em batch, ou embutindo. Sobre REST, é um endpoint de lista mais uma chamada HTTP por item — pior que a versão de banco, porque a latência de rede engole a latência de query; endpoints compostos, endpoints de batch e linguagens de consulta existem em boa parte para matar isso.'
  - question: 'Lazy loading é sempre errado?'
    answer: 'Não — ele é errado dentro de loops. Lazy loading é exatamente certo quando os dados relacionados raramente são necessários: pague por eles no único registro que precisa, em vez de pagar de forma eager por todos os N. A disciplina é saber o padrão de acesso de cada tela: relações sempre necessárias carregam eager, as raramente necessárias carregam lazy, e qualquer coisa dentro de um loop passa por auditoria.'
codeLanguages: [javascript, dart, swift, kotlin]
externalAuthorities:
  - name: 'DataLoader pattern — graphql-js documentation'
    url: 'https://www.graphql-js.org/docs/n1-dataloader/'
  - name: 'Solving the N+1 problem for GraphQL through batching — Shopify Engineering'
    url: 'https://shopify.engineering/solving-the-n-1-problem-for-graphql-through-batching'
  - name: 'N+1 queries — Sentry performance issue documentation'
    url: 'https://docs.sentry.io/product/issues/issue-details/performance-issues/n-one-queries/'
  - name: 'SDK query documentation (include)'
    url: 'https://docs.parseplatform.org/js/guide/#relational-data'
cta:
  title: 'Uma requisição onde outros fazem cem'
  text: 'Os SDKs do Back4app fazem da correção o idioma padrão: include() busca as relações na mesma requisição, agrupadas no servidor, com pointers mantendo os joins baratos. O loop N+1 simplesmente não tem motivo para existir na sua base de código.'
  linkText: 'Comece grátis'
  linkUrl: 'https://www.back4app.com/signup'
author: 'Back4app Engineering'
publishedDate: '2026-08-21'
translationKey: n-plus-one-query-problem
---

**O problema de consultas N+1 é um padrão em que buscar N registros dispara uma query extra por registro — N+1 idas ao banco em vez de uma ou duas.** É o bug sério de performance mais comum em aplicações apoiadas em dados, e o mais camuflado: cada query individual é rápida, o código lê perfeitamente, e a página funciona — até o dia em que os volumes reais de dados chegam.

## Principais pontos

| Pergunta | Resposta |
| --- | --- |
| O formato | 1 query para a lista + N queries para relações = N+1 idas ao banco |
| A causa | Lazy loading tocado dentro de um loop — queries invisíveis por iteração |
| A assinatura | Muitas queries *rápidas e idênticas* — logs de queries lentas nunca a veem |
| As saídas | Eager loading · joins · IN em batch · loaders por requisição |
| O multiplicador | Latência de ida e volta × tamanho da página — brutal sobre a rede |

## O bug, às claras

```javascript
// 1 query: busca 100 posts
const posts = await postRepo.findRecent(100);

for (const post of posts) {
  // +1 query POR POST — post.author parece uma propriedade,
  // mas o lazy loading roda: SELECT * FROM users WHERE id = ?
  render(post.title, (await post.author).name);
}
// Log de queries: 1 + 100 = 101 idas ao banco para renderizar uma página
```

A correção, como os SDKs a expressam — declare a relação de antemão, e a plataforma a busca na mesma requisição:

**JavaScript:**

```javascript
// JavaScript / Node.js — Back4app JS SDK
// The fix: fetch the relation in the same request — 1 query, not N+1
const query = new Parse.Query('Comment');
query.equalTo('post', post);
query.include('author');                    // eager-load the pointer
const comments = await query.find();        // one round trip, total

comments.forEach((c) =>
  render(c.get('text'), c.get('author').get('username')) // already loaded
);
```

**Flutter:**

```dart
// Flutter / Dart — Back4app Flutter SDK
// The fix: fetch the relation in the same request — 1 query, not N+1
final query = QueryBuilder<ParseObject>(ParseObject('Comment'))
  ..whereEqualTo('post', post.toPointer())
  ..includeObject(['author']);              // eager-load the pointer
final response = await query.query();       // one round trip, total
```

**Swift:**

```swift
// iOS / Swift — Back4app Swift SDK
// The fix: fetch the relation in the same request — 1 query, not N+1
let query = Comment.query("post" == post)
  .include("author")                        // eager-load the pointer
query.find { result in
  if case .success(let comments) = result { // one round trip, total
    comments.forEach { render($0.text, $0.author?.username) }
  }
}
```

**Kotlin:**

```kotlin
// Android / Kotlin — Back4app Android SDK
// The fix: fetch the relation in the same request — 1 query, not N+1
val query = ParseQuery.getQuery<ParseObject>("Comment")
query.whereEqualTo("post", post)
query.include("author")                     // eager-load the pointer
query.findInBackground { comments, e ->     // one round trip, total
  if (e == null) comments.forEach {
    render(it.getString("text"), it.getParseObject("author")?.getString("username"))
  }
}
```

## O custo, na ponta do lápis

A aritmética que os explicadores pulam — tempo total ≈ query da lista + (N × latência de ida e volta):

| Tamanho da página N | @1 ms/query | @5 ms/query | Em batch (1–2 queries) |
| --- | --- | --- | --- |
| 10 | ~11 ms | ~55 ms | ~10 ms |
| 100 | ~101 ms | ~505 ms | ~12 ms |
| 1.000 | ~1,0 s | ~5,0 s | ~20 ms |

Correções publicadas batem com a matemática: páginas caindo de 1,4 s para 0,16 s, endpoints acelerando 30× ou mais. Dois corolários que valem fixar: **paginação limita N** — um tamanho de página limitado limita o raio da explosão antes mesmo da correção de verdade; e **a versão em rede é pior** — quando cada uma das N chamadas é uma requisição HTTP em vez de uma query local, multiplique por dezenas de milissegundos em vez de um dígito.

## As quatro saídas

```mermaid
flowchart LR
  accTitle: Cascata N mais 1 versus busca em batch
  accDescr: O padrão N+1 dispara uma query de lista seguida de uma query por registro em sequência; o padrão em batch dispara a query de lista e uma única query buscando todos os registros relacionados de uma vez.
  subgraph P["Cascata N+1"]
    a["Query da lista"] --> b["Query por registro<br/>× N, uma após a outra"]
  end
  subgraph B["Em batch"]
    c["Query da lista"] --> d["UMA query para todas as relações<br/>WHERE id IN (…)"]
  end
  P -.->|"a correção"| B
```

| Correção | Como | Escolha quando | Cuidado com |
| --- | --- | --- | --- |
| Eager loading / include | Declare as relações na query | A tela sempre precisa da relação | Incluir demais incha os payloads |
| Join | Uma instrução busca os dois | Motor relacional, leituras em formato de relatório | Explosão de linhas em joins largos |
| IN em batch | Colete as chaves, busque uma vez | Qualquer stack, até feito à mão | Uma ida extra ao banco (aceitável) |
| Loader por requisição | Batch automático na execução | Resolvers GraphQL, código em camadas | Precisa ser por requisição, não global |

A última linha é o caso famoso do GraphQL: resolvers disparam por objeto pai, recriando o loop no servidor, e o [padrão loader](https://www.graphql-js.org/docs/n1-dataloader/) — coletar chaves num tick, buscar uma vez, memoizar por requisição — é a [cura padrão da indústria](https://shopify.engineering/solving-the-n-1-problem-for-graphql-through-batching). Uma regra sutil vem junto: loaders têm *escopo de requisição*; um global vira um cache velho com bugs de autorização.

## Detecção: cace as queries rápidas

A assinatura do N+1 é o inverso do trabalho normal de performance: você procura **muitas queries rápidas e idênticas**, não uma lenta — motivo pelo qual logs de queries lentas, a ferramenta usual, [nunca a veem](https://docs.sentry.io/product/issues/issue-details/performance-issues/n-one-queries/). Os métodos que veem: log de debug do ORM (a mesma instrução, N parâmetros diferentes, uma renderização); as visões de spans do APM, onde a cascata de spans curtos idênticos é inconfundível; e o mais barato de todos — conte as queries de um carregamento de página em desenvolvimento, com um limiar na cabeça: uma página de lista deve custar queries em um dígito, não em múltiplos das suas linhas. O gêmeo do lado da escrita merece a mesma auditoria: um loop de insert por item é o N+1 das escritas, corrigido com operações em bulk.

## Casos de uso comuns

- **Listas com autores, donos ou status** — o lar canônico: todo feed, inbox e tabela que junta pessoas a itens.
- **APIs GraphQL** — campos de lista aninhados são estruturalmente N+1 até existirem loaders.
- **Bancos de documentos** — um find por documento referenciado; corrigido com include, IN em batch ou embutindo, conforme as [regras de modelagem](/glossary/pt/joins-em-bancos-de-documentos/).
- **Fan-outs de microsserviços** — uma lista do serviço A, uma chamada HTTP ao serviço B por item; endpoints compostos e BFFs existem para acabar com isso.
- **Jobs em background** — o loop que processa 10.000 registros com duas queries cada, custando horas em silêncio.

## Lazy vs. eager loading: matriz de decisão

| Carregue eager quando… | Fique no lazy quando… |
| --- | --- |
| A relação renderiza em toda linha | A relação está atrás de um clique |
| O loop é o padrão de acesso | O acesso é um registro por vez |
| N é do tamanho da página ou maior | N é garantidamente minúsculo |
| A latência é visível ao usuário | Uma tarefa em background tolera a espera |
| Você acabou de corrigir este bug aqui | Você mediu, em vez de supor |

E a regra de auditoria permanente que sobrevive a qualquer matriz: **toda relação tocada dentro de um loop é culpada até o log de queries provar o contrário.**

## Limitações e trade-offs

- **Eager loading pode corrigir demais.** Incluir relações pesadas em todo lugar troca N+1 por payloads inchados e joins largos — inclua o que a tela renderiza, não o grafo inteiro.
- **Joins têm seu próprio penhasco.** Joins um-para-muitos duplicam as linhas do pai por filho; com fan-out alto, a busca em batch de duas queries vence o join único.
- **Loaders adicionam maquinário.** Escopo por requisição, invalidação de cache dentro da requisição e janelas de batching são código de verdade — o preço do batch automático.
- **Frameworks o reintroduzem em silêncio.** Serializers, helpers de template e reviews de "só mais um campo" são como páginas corrigidas regridem; a checagem de contagem de queries pertence ao CI, não à memória.
- **A correção é por caminho, não global.** N+1 é um bug de padrão de acesso; cada tela nova refaz a pergunta.

## O N+1 no Back4app

O Back4app é uma plataforma open-source de Backend as a Service (BaaS) que combina banco de dados gerenciado, APIs REST e GraphQL geradas automaticamente, autenticação, armazenamento de arquivos e funções serverless com Cloud Code. Seus SDKs fazem da correção o idioma, não a remediação: relações são Pointers tipados, e o [`include()`](https://docs.parseplatform.org/js/guide/#relational-data) — as abas de código acima — as busca na mesma requisição, agrupadas no servidor. A API GraphQL resolve queries aninhadas sem fan-out por campo, e os padrões de paginação limitam N antes de ele criar dentes. O loop que causa o N+1 não tem forma natural de ser escrito — que é o melhor tipo de correção: a que ninguém precisa lembrar.
