System Design · Modul 3: Daten speichern

Lektion 3 von 7Übung 14 Min.

Indizes und Abfragen

Lernziele
  • verstehen, wie ein Index Abfragen beschleunigt
  • die Kosten von Indizes beim Schreiben und Speichern einschätzen
  • zusammengesetzte Indizes in der richtigen Reihenfolge anlegen
  • langsame Abfragen mit EXPLAIN analysieren und typische Probleme wie N+1 erkennen

Die häufigste Ursache für langsame Webanwendungen ist nicht die Programmiersprache, nicht der Server und nicht das Framework, sondern eine langsame Datenbankabfrage. Und die häufigste Ursache für eine langsame Abfrage ist ein fehlender oder falscher Index. Die gute Nachricht: Solche Probleme lassen sich meist mit wenig Aufwand beheben, wenn man versteht, wie Indizes funktionieren und wie man die Datenbank fragt, was sie gerade tut.

1. Was ein Index ist

Definition 3.1
Index
Eine zusätzliche Datenstruktur, die Werte einer oder mehrerer Spalten sortiert mit Verweisen auf die zugehörigen Zeilen speichert. Wie das Stichwortverzeichnis eines Buches erlaubt sie, passende Zeilen zu finden, ohne die ganze Tabelle zu lesen. Der Standard in PostgreSQL ist der B-Baum, der Suchen, Bereiche und Sortierungen effizient unterstützt.

Ohne Index muss die Datenbank für eine Abfrage wie „finde den Nutzer mit dieser E-Mail-Adresse“ jede Zeile der Tabelle lesen und prüfen. Bei tausend Zeilen fällt das nicht auf, bei zehn Millionen dauert es Sekunden. Mit einem B-Baum-Index findet sie den Eintrag in wenigen Schritten, weil sie wie in einem Telefonbuch gezielt zur richtigen Stelle springt. Die Zahl der Schritte wächst nur logarithmisch mit der Tabellengröße: Bei einer Million Zeilen sind es etwa zwanzig, bei einer Milliarde etwa dreißig. Probieren Sie es aus:

SimulatorMit und ohne Index
SELECT * FROM nutzer WHERE email = 'anna@beispiel.de';

1.000.000

gelesene Einträge

~50 ms

geschätzte Dauer

Ohne passenden Index liest die Datenbank jede Zeile (Sequential Scan).

2. Indizes kosten auch etwas

Wenn Indizes so wirksam sind, warum legt man nicht auf jede Spalte einen an? Weil jeder Index bei jedem Schreibvorgang aktualisiert werden muss und Speicherplatz belegt. Eine Tabelle mit zehn Indizes wird bei jedem Einfügen elfmal beschrieben. Bei schreiblastigen Tabellen, etwa für Protokolle oder Ereignisse, können zu viele Indizes die Schreibleistung spürbar senken. Die Kunst liegt darin, genau die Indizes anzulegen, die die wichtigen Abfragen brauchen – nicht mehr und nicht weniger.

Tab. 3.1Wann ein Index hilft
AbfrageIndex hilft?Hinweis
WHERE email = …jaklassischer Fall, oft als eindeutiger Index
WHERE erstellt_am > …jaBereichsabfragen funktionieren mit B-Baum
ORDER BY erstellt_am LIMIT 20jaIndex liefert bereits sortiert
WHERE lower(email) = …nur mit Index auf lower(email)Funktionen verhindern normale Indizes
WHERE name LIKE '%muster%'nein (B-Baum)dafür Volltext- oder Trigramm-Index
WHERE status = 'aktiv' bei 95 % aktivkaumgeringe Selektivität – Datenbank liest lieber alles
Klick-GrafikWie ein B-Baum-Index sucht
Wurzel: A–M | N–ZA–F | G–MN–S | T–ZAnna … FritzGreta … MiaNora … Sven
Schritt 1/3 · Gesucht: „Lena“: Ohne Index müsste die Datenbank alle Zeilen lesen. Mit Index beginnt sie an der Wurzel.

3. Zusammengesetzte Indizes

Viele Abfragen filtern nach mehreren Spalten, etwa „alle Buchungen eines Salons an einem Tag“. Dafür gibt es zusammengesetzte Indizes über mehrere Spalten. Dabei ist die Reihenfolge entscheidend. Ein Index auf (salon_id, datum) funktioniert wie ein Telefonbuch, das zuerst nach Nachnamen und dann nach Vornamen sortiert ist: Er hilft bei Abfragen nach dem Salon allein und nach Salon und Datum, aber kaum bei Abfragen nur nach dem Datum. Als Faustregel gehören Spalten mit Gleichheitsbedingungen nach vorn und Spalten mit Bereichen oder Sortierung nach hinten.

Indizes folgen den Abfragen, nicht dem Datenmodell. Schauen Sie, welche Abfragen häufig und wichtig sind, und legen Sie genau dafür passende Indizes an – in der Reihenfolge: erst Gleichheit, dann Bereich oder Sortierung.

4. Abfragen analysieren

Die Datenbank verrät, wie sie eine Abfrage ausführt. In PostgreSQL zeigt der Befehl EXPLAIN den geplanten Ablauf, EXPLAIN ANALYZE führt die Abfrage tatsächlich aus und zeigt die gemessenen Zeiten. Die wichtigsten Hinweise darin: Ein „Seq Scan“ auf einer großen Tabelle bedeutet, dass jede Zeile gelesen wird. Ein „Index Scan“ oder „Index Only Scan“ bedeutet, dass ein Index genutzt wird. Und große Abweichungen zwischen geschätzten und tatsächlichen Zeilenzahlen deuten auf veraltete Statistiken hin. Zusätzlich lohnt die Erweiterung pg_stat_statements, die für alle Abfragen misst, wie oft sie laufen und wie viel Zeit sie insgesamt kosten. Sie zeigt, welche Abfragen die größte Last erzeugen – oft nicht die langsamste einzelne, sondern eine mittelschnelle, die tausendmal pro Minute läuft.

5. Das N+1-Problem

Ein Problem, das in fast jeder Anwendung mit einem ORM auftaucht, ist das N+1-Problem. Eine Seite zeigt zwanzig Kurse mit dem jeweiligen Namen der Lehrkraft. Der Code lädt zuerst die zwanzig Kurse mit einer Abfrage und dann für jeden Kurs die Lehrkraft mit einer eigenen Abfrage – insgesamt 21 Abfragen statt einer oder zwei. Jede einzelne ist schnell, aber zusammen mit der Netzwerkzeit zur Datenbank wird die Seite spürbar langsam, und unter Last vervielfacht sich das Problem. Die Lösung ist, zusammengehörige Daten gemeinsam zu laden, etwa mit einem JOIN oder den Funktionen des ORM zum vorausschauenden Laden. Viele Überwachungswerkzeuge erkennen N+1-Muster automatisch.

Weitere häufige Ursachen langsamer Abfragen sind das Laden aller Spalten, obwohl nur wenige gebraucht werden, fehlende Begrenzungen bei Listen – eine Seite ohne LIMIT lädt irgendwann hunderttausend Zeilen – und die Paginierung mit großen OFFSET-Werten, bei der die Datenbank alle übersprungenen Zeilen trotzdem lesen muss. Für lange Listen ist die Paginierung über einen Schlüssel, etwa „die nächsten 20 nach ID 4711“, deutlich schneller.

6. Eine Routine für schnelle Abfragen

In der Praxis bewährt sich eine einfache Routine. Beim Entwurf werden die wichtigsten Abfragen notiert und passende Indizes geplant. Beim Entwickeln wird jede neue Abfrage einmal mit EXPLAIN geprüft. Im Betrieb zeigt pg_stat_statements regelmäßig die teuersten Abfragen, und das Protokoll langsamer Abfragen meldet Ausreißer. Nicht genutzte Indizes werden gelegentlich entfernt, weil sie nur Schreibleistung kosten. Mit dieser Routine bleiben die meisten Anwendungen auch bei wachsenden Datenmengen schnell, lange bevor Themen wie Replikation oder Partitionierung relevant werden, um die es in der nächsten Lektion geht.

Ein Beispiel aus der Praxis zeigt, wie groß die Wirkung sein kann. Eine Lernplattform lud die Übersichtsseite eines Kurses in über zwei Sekunden. Die Analyse mit pg_stat_statements zeigte zwei Ursachen: Für jede der vierzig Lektionen wurde der Fortschritt des Nutzers einzeln abgefragt, und diese Abfrage filterte nach Nutzer und Lektion ohne passenden Index. Ein zusammengesetzter Index auf (nutzer_id, lektion_id) und eine gemeinsame Abfrage für alle Lektionen senkten die Ladezeit auf unter hundert Millisekunden. Es waren weder neue Server noch ein Cache nötig – nur ein besseres Verständnis dafür, was die Datenbank tatsächlich tat. Solche Verbesserungen sind in fast jeder gewachsenen Anwendung möglich und gehören zu den lohnendsten Maßnahmen überhaupt.

Ein Hinweis zum Schluss: Wiederholen Sie diese Analyse regelmäßig, denn mit wachsenden Datenmengen und neuen Funktionen verändern sich die teuersten Abfragen. Was heute schnell ist, kann in einem Jahr zum Engpass werden – und umgekehrt können Indizes überflüssig werden, die niemand mehr braucht.

Legen Sie sich dafür am besten eine kleine Checkliste an und gehen Sie sie bei jeder größeren Änderung am Datenmodell durch: Welche neuen Abfragen entstehen, welche Indizes brauchen sie, und welche alten werden überflüssig?

Quellen und weiterführende Literatur
  1. [1]PostgreSQL Global Development Group (2026): Documentation – Indexes; Using EXPLAIN; pg_stat_statements.
  2. [2]Winand, M. (2012): SQL Performance Explained. Wien: Eigenverlag (use-the-index-luke.com).
  3. [3]Bayer, R.; McCreight, E. (1972): Organization and Maintenance of Large Ordered Indexes. Acta Informatica 1(3), 173–189.
  4. [4]Kleppmann, M. (2017): Designing Data-Intensive Applications. Sebastopol: O'Reilly, Kap. 3.

Stand: September 2026. Kursmaterial der Klarwerk Akademie. Zahlen zu Latenzen und Kosten sind Größenordnungen zur Orientierung, keine Messwerte.

Abschlussquiz

Drei Fragen – dann ist die Lektion geschafft.

Frage 1 von 3

Warum hilft ein normaler Index bei WHERE lower(email) = … nicht?