---
term: 'N+1-Abfrageproblem'
seoTitle: 'N+1-Abfrageproblem: Ursachen, Erkennung und Lösungen'
headline: 'Was ist das N+1-Abfrageproblem?'
slug: n-plus-1-problem
category: api-realtime
shortDefinition: 'Das N+1-Abfrageproblem ist ein Muster, bei dem das Laden von N Datensätzen eine Extra-Abfrage pro Datensatz auslöst – N+1 Roundtrips statt ein oder zwei.'
relatedTerms:
  - relational-queries-document-databases
  - graphql-vs-rest
  - overfetching-underfetching
  - api-payload-optimization
  - database-index
contrastsWith:
  - overfetching-underfetching
faq:
  - question: 'Was ist das N+1-Abfrageproblem, einfach erklärt?'
    answer: 'Sie holen eine Liste von N Datensätzen mit einer Abfrage, und dann setzt Ihr Code pro Datensatz eine weitere Abfrage für dessen verwandte Daten ab – aus hundert Beiträgen werden hunderteine Abfragen. Jede einzelne Abfrage ist für sich schnell, und genau deshalb versteckt sich das Problem: Nichts ist langsam genug, um jemanden zu beunruhigen, bis die Seite Hunderte Roundtrips macht.'
  - question: 'Wodurch entstehen N+1-Abfragen?'
    answer: 'Durch Lazy Loading als Standard. ORMs und SDKs lassen Sie Relationen wie Objekteigenschaften ansprechen – post.author – und führen beim Zugriff transparent eine Abfrage aus. Packen Sie diesen Zugriff in eine Schleife über N Ergebnisse, haben Sie stillschweigend N Abfragen geschrieben. Dieselbe Abstraktion, die den Datenzugriff angenehm gemacht hat, hat die Roundtrips unsichtbar gemacht.'
  - question: 'Wie behebt man das N+1-Problem?'
    answer: 'Vier Standardauswege, je nach Fall zu wählen: Eager Loading – der Abfrage vorab sagen, dass sie die Relation mitladen soll; ein Join, der beides in einer Anweisung holt; Batching – die N Fremdschlüssel sammeln und in einer "enthalten-in"-Abfrage laden; und anfragegebundene Loader, die automatisch bündeln. Alle vier machen aus N+1 Roundtrips einen oder zwei.'
  - question: 'Wie viel langsamer ist N+1 wirklich?'
    answer: 'Rechnen Sie es aus: Jeder Roundtrip kostet eine bis fünf Millisekunden, bevor die Abfrage überhaupt läuft, also ergeben 100 Zeilen zu 5 ms eine halbe Sekunde extra gegenüber einer gebündelten Abfrage von rund 10 ms. Veröffentlichte Praxisberichte melden Seiten, die von etwa 1,4 Sekunden auf 0,16 gehen, und API-Endpoints, die um das Dreißigfache schneller werden. Die Rechnung verschlechtert sich linear mit der Seitengröße – und über Netzwerke katastrophal.'
  - question: 'Warum tritt das N+1-Problem in GraphQL so häufig auf?'
    answer: 'Weil Resolver pro Feld und pro Objekt laufen. Eine Abfrage nach Beiträgen samt Autoren führt den Beitrags-Resolver einmal aus und den Autoren-Resolver N-mal – die ORM-Schleife, serverseitig wiedergeboren. Das kanonische Gegenmittel ist das Loader-Pattern: ein anfragegebundener Bündler, der die Autoren-IDs während der Ausführung einsammelt und einen gebündelten Ladevorgang absetzt, für die Dauer der Anfrage gemerkt.'
  - question: 'Wie erkennt man N+1-Abfragen?'
    answer: 'Suchen Sie nach vielen schnellen, identischen Abfragen, nicht nach einer langsamen – das ist die Signatur. Das Debug-Logging des ORM zeigt dieselbe Anweisung mit unterschiedlichen Parametern; Logs für langsame Abfragen übersehen sie vollständig, weil jede Abfrage kurz ist; APM-Werkzeuge markieren das Span-Muster ausdrücklich. Die Gewohnheit, die es früh aufdeckt: das Abfrage-Log für einen Seitenaufbau lesen und zählen.'
  - question: 'Tritt das N+1-Problem auch in Dokumentdatenbanken und REST-APIs auf?'
    answer: 'Überall, wo Daten Relationen haben. In Dokumentspeichern ist es ein Suchvorgang pro referenziertem Dokument – behoben durch Include-Mechanismen, gebündelte "enthalten-in"-Abfragen oder Einbetten. Über REST ist es ein Listen-Endpoint plus ein HTTP-Aufruf pro Element – schlimmer als die Datenbankvariante, weil die Netzwerklatenz die Abfragelatenz um ein Vielfaches übersteigt; zusammengesetzte Endpoints, Batch-Endpoints und Abfragesprachen existieren weitgehend, um das zu beenden.'
  - question: 'Ist Lazy Loading immer falsch?'
    answer: 'Nein – es ist in Schleifen falsch. Lazy Loading ist genau richtig, wenn verwandte Daten selten gebraucht werden: Zahlen Sie dafür bei dem einen Datensatz, der sie braucht, statt vorab für alle N. Die Disziplin besteht darin, das Zugriffsmuster jedes Bildschirms zu kennen: immer benötigte Relationen werden vorab geladen, selten benötigte träge, und alles innerhalb einer Schleife wird geprüft.'
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: 'Eine Anfrage, wo andere hundert machen'
  text: 'Back4app-SDKs machen die Lösung zum Standardidiom: include() lädt Relationen in derselben Anfrage, serverseitig gebündelt, und Pointers halten die Joins günstig. Die Schleife, die N+1 verursacht, hat in Ihrer Codebasis schlicht keinen Grund zu existieren.'
  linkText: 'Kostenlos starten'
  linkUrl: 'https://www.back4app.com/signup'
author: 'Back4app Engineering'
publishedDate: '2026-09-24'
translationKey: n-plus-one-query-problem
---

**Das N+1-Abfrageproblem ist ein Muster, bei dem das Laden von N Datensätzen eine Extra-Abfrage pro Datensatz auslöst – N+1 Roundtrips statt ein oder zwei.** Es ist der häufigste ernsthafte Performance-Fehler in datengetriebenen Anwendungen und zugleich der am besten getarnte: Jede einzelne Abfrage ist schnell, der Code liest sich einwandfrei, und die Seite funktioniert – bis echte Datenmengen eintreffen.

## Das Wichtigste in Kürze

| Frage | Antwort |
| --- | --- |
| Die Form | 1 Abfrage für die Liste + N Abfragen für Relationen = N+1 Roundtrips |
| Die Ursache | Lazy Loading, in einer Schleife berührt – unsichtbare Abfragen pro Durchlauf |
| Die Signatur | Viele *schnelle identische* Abfragen – Slow-Query-Logs sehen sie nie |
| Die Auswege | Eager Loading · Joins · gebündeltes IN · anfragegebundene Loader |
| Der Multiplikator | Roundtrip-Latenz × Seitengröße – über Netzwerke brutal |

## Der Fehler, offen gezeigt

```javascript
// 1 Abfrage: 100 Beiträge laden
const posts = await postRepo.findRecent(100);

for (const post of posts) {
  // +1 Abfrage PRO BEITRAG — post.author sieht aus wie eine Eigenschaft,
  // aber Lazy Loading läuft: SELECT * FROM users WHERE id = ?
  render(post.title, (await post.author).name);
}
// Abfrage-Log: 1 + 100 = 101 Roundtrips für einen Seitenaufbau
```

Die Lösung, wie SDKs sie ausdrücken – die Relation vorab deklarieren, und die Plattform lädt sie in derselben Anfrage:

**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"))
  }
}
```

## Die Kosten, ausgerechnet

Die Arithmetik, die Erklärtexte überspringen – Gesamtzeit ≈ Listenabfrage + (N × Roundtrip-Latenz):

| Seitengröße N | @1 ms/Abfrage | @5 ms/Abfrage | Gebündelt (1–2 Abfragen) |
| --- | --- | --- | --- |
| 10 | ~11 ms | ~55 ms | ~10 ms |
| 100 | ~101 ms | ~505 ms | ~12 ms |
| 1.000 | ~1,0 s | ~5,0 s | ~20 ms |

Veröffentlichte Korrekturen bestätigen die Rechnung: Seiten, die von 1,4 s auf 0,16 s fallen, Endpoints, die um das Dreißigfache und mehr schneller werden. Zwei Folgerungen, die man sich anheften sollte: **Paginierung deckelt N** – eine begrenzte Seitengröße begrenzt den Schadensradius schon vor der eigentlichen Lösung; und **die Netzwerkvariante ist schlimmer** – wenn jeder der N Aufrufe eine HTTP-Anfrage statt einer lokalen Abfrage ist, multiplizieren Sie mit Dutzenden Millisekunden statt mit einstelligen Werten.

## Die vier Auswege

```mermaid
flowchart LR
  accTitle: N-plus-1-Wasserfall gegenüber gebündeltem Laden
  accDescr: Das N+1-Muster setzt eine Listenabfrage ab, gefolgt von einer Abfrage pro Datensatz nacheinander; das gebündelte Muster setzt die Listenabfrage ab und eine einzige Abfrage, die alle verwandten Datensätze auf einmal lädt.
  subgraph P["N+1-Wasserfall"]
    a["Listenabfrage"] --> b["Abfrage pro Datensatz<br/>× N, eine nach der anderen"]
  end
  subgraph B["Gebündelt"]
    c["Listenabfrage"] --> d["EINE Abfrage für alle Relationen<br/>WHERE id IN (…)"]
  end
  P -.->|"die Lösung"| B
```

| Lösung | Wie | Greifen Sie zu, wenn | Achten Sie auf |
| --- | --- | --- | --- |
| Eager Loading / Include | Relationen an der Abfrage deklarieren | Der Bildschirm braucht die Relation immer | Zu viel Include bläht Payloads auf |
| Join | Eine Anweisung holt beides | Relationale Engine, berichtsartige Lesezugriffe | Zeilenexplosion bei breiten Joins |
| Gebündeltes IN | Schlüssel sammeln, einmal laden | Jeder Stack, auch handgebaut | Ein zusätzlicher Roundtrip (in Ordnung) |
| Anfragegebundener Loader | Automatisches Bündeln während der Ausführung | GraphQL-Resolver, geschichteter Code | Muss pro Anfrage gelten, nicht global |

Die letzte Zeile ist der berühmte Fall von GraphQL: Resolver feuern pro Elternobjekt und erzeugen die Schleife serverseitig neu, und das [Loader-Pattern](https://www.graphql-js.org/docs/n1-dataloader/) – Schlüssel in einem Tick sammeln, einmal laden, pro Anfrage merken – ist das [branchenübliche Gegenmittel](https://shopify.engineering/solving-the-n-1-problem-for-graphql-through-batching). Eine feine Regel reitet mit: Loader gelten *pro Anfrage*; ein globaler wird zum veralteten Cache mit Autorisierungsfehlern.

## Erkennung: Jagen Sie die schnellen Abfragen

Die N+1-Signatur ist gegenüber der üblichen Performance-Arbeit invertiert: Sie suchen **viele schnelle, identische Abfragen**, nicht eine langsame – weshalb Logs für langsame Abfragen, das gewohnte Werkzeug, [sie nie sehen](https://docs.sentry.io/product/issues/issue-details/performance-issues/n-one-queries/). Die Methoden, die es tun: Debug-Logging des ORM (dieselbe Anweisung, N verschiedene Parameter, ein Seitenaufbau); Span-Ansichten im APM, wo der Wasserfall identischer kurzer Spans unverkennbar ist; und die billigste von allen – zählen Sie in der Entwicklung die Abfragen für einen Seitenaufruf, mit einem Schwellenwert im Kopf: Eine Listenseite sollte einstellig viele Abfragen kosten, nicht ein Vielfaches ihrer Zeilen. Der Zwilling auf der Schreibseite verdient seine Prüfung ebenso: Eine Schleife mit einem Insert pro Element ist N+1 fürs Schreiben, behoben durch Massenoperationen.

## Typische Anwendungsfälle

- **Listenansichten mit Autoren, Besitzern oder Status** – die kanonische Heimat: jeder Feed, jeder Posteingang, jede Tabelle, die Personen mit Objekten verknüpft.
- **GraphQL-APIs** – verschachtelte Listenfelder sind strukturell N+1, solange es keine Loader gibt.
- **Dokumentdatenbanken** – ein Suchvorgang pro referenziertem Dokument; behoben mit Include, gebündeltem IN oder Einbetten, gemäß den [Modellierungsregeln](/glossary/de/joins-in-dokumentdatenbanken/).
- **Microservice-Fan-outs** – eine Liste von Service A, ein HTTP-Aufruf pro Element an Service B; zusammengesetzte Endpoints und BFFs existieren, um damit Schluss zu machen.
- **Hintergrundjobs** – die Schleife, die 10.000 Datensätze mit je zwei Abfragen verarbeitet und still Stunden kostet.

## Lazy vs. Eager Loading: eine Entscheidungsmatrix

| Vorab laden, wenn… | Träge bleiben, wenn… |
| --- | --- |
| Die Relation in jeder Zeile gerendert wird | Die Relation hinter einem Klick liegt |
| Die Schleife das Zugriffsmuster ist | Der Zugriff Datensatz für Datensatz erfolgt |
| N seitengroß oder größer ist | N garantiert winzig ist |
| Die Latenz beim Benutzer ankommt | Eine Hintergrundaufgabe sich Verzug leisten kann |
| Sie genau diesen Fehler hier gerade behoben haben | Sie gemessen und nicht vermutet haben |

Und die stehende Prüfregel, die jede Matrix überlebt: **Jede Relation, die in einer Schleife berührt wird, ist schuldig, bis das Abfrage-Log das Gegenteil beweist.**

## Grenzen und Trade-offs

- **Eager Loading kann überkorrigieren.** Schwere Relationen überall mitzuladen tauscht N+1 gegen aufgeblähte Payloads und breite Joins – laden Sie mit, was der Bildschirm rendert, nicht den ganzen Graphen.
- **Joins haben ihre eigene Klippe.** Eins-zu-viele-Joins vervielfachen Elternzeilen pro Kind; bei hohem Fan-out schlägt der gebündelte Ladevorgang aus zwei Abfragen den einzelnen Join.
- **Loader bringen Maschinerie mit.** Gültigkeit pro Anfrage, Cache-Invalidierung innerhalb der Anfrage und Bündelungsfenster sind echter Code – der Preis für automatisches Batching.
- **Frameworks führen es stillschweigend wieder ein.** Serializer, Template-Helfer und "nur noch ein Feld"-Reviews sind der Weg, auf dem reparierte Seiten zurückfallen; die Prüfung der Abfragezahl gehört in die CI, nicht ins Gedächtnis.
- **Die Lösung gilt pro Pfad, nicht global.** N+1 ist ein Fehler im Zugriffsmuster; jeder neue Bildschirm stellt die Frage neu.

## N+1 mit Back4app

Back4app ist eine Open-Source-Plattform für Backend as a Service (BaaS), die eine verwaltete Datenbank, automatisch generierte REST- und GraphQL-APIs, Authentifizierung, Dateispeicher und Serverless-Funktionen mit Cloud Code kombiniert. Seine SDKs machen die Lösung zum Idiom statt zur Nachbesserung: Relationen sind typisierte Pointers, und [`include()`](https://docs.parseplatform.org/js/guide/#relational-data) – die Code-Tabs oben – lädt sie in derselben Anfrage, serverseitig gebündelt. Die GraphQL-API löst verschachtelte Abfragen ohne Fan-out pro Feld auf, und die Standardwerte der Paginierung begrenzen N, bevor es Zähne bekommt. Die Schleife, die N+1 verursacht, lässt sich gar nicht auf natürliche Weise schreiben – und das ist die beste Art von Lösung: die, an die sich niemand erinnern muss.
