Eine Abfrage, die auf dem Entwicklungsrechner sofort antwortet, kann in der Produktion plötzlich mehrere Sekunden dauern. Oft liegt das nicht am SQL selbst, sondern daran, dass die Datenbank jede Zeile durchsuchen muss. Ein gut geplanter SQL-Index verkürzt solche Zugriffe deutlich, kostet aber Speicherplatz und macht Schreibvorgänge aufwendiger.
Die wichtigsten Entscheidungen auf einen Blick
- Indizes beschleunigen vor allem Filter, Verknüpfungen und Sortierungen.
- Ein B-Tree passt zu den meisten Abfragen mit Gleichheits- und Bereichsbedingungen.
- Bei mehreren Spalten zählt die Reihenfolge wegen des Leftmost-Prefix-Prinzips.
- Jeder zusätzliche Index verursacht Speicher- und Schreibkosten.
- EXPLAIN zeigt, ob der Optimizer den Index tatsächlich verwendet.

Was ein SQL-Index tatsächlich leistet
Ein Index ist eine zusätzliche Datenstruktur, die Suchschlüssel aus einer oder mehreren Tabellenspalten organisiert. Die Datenbank kann dadurch gezielt zu passenden Datensätzen springen, statt die Tabelle vollständig von Anfang bis Ende zu lesen. Bei großen Tabellen ist dieser Unterschied oft entscheidender als eine kleine Änderung am Anwendungscode.
Die häufigste Variante ist der B-Tree, genauer gesagt meist ein B+-Baum. Er eignet sich für Abfragen wie WHERE kunden_id = 42, für Bereiche wie WHERE preis BETWEEN 50 AND 100 und häufig auch für ORDER BY. Die Suche wächst dabei nicht linear mit jeder zusätzlichen Zeile, sondern wesentlich effizienter über die Baumstruktur.
Ein Index enthält jedoch nicht automatisch alle Daten einer Zeile. Häufig findet die Datenbank zunächst die passenden Schlüssel und liest anschließend die vollständigen Datensätze aus der Tabelle. Ein sogenannter Covering Index kann zusätzlich benötigte Spalten enthalten, sodass dieser zweite Zugriff teilweise entfällt. Ob das sinnvoll ist, hängt stark vom Datenbanksystem und der konkreten Abfrage ab.
Welche Indexarten in der Entwicklung wichtig sind
B-Tree für typische Geschäftsabfragen
Für Kundennummern, Zeitstempel, Bestellstatus oder numerische Bereiche ist ein B-Tree meist der erste Kandidat. Er unterstützt sowohl exakte Vergleiche als auch Bereichssuchen und kann oft eine Sortierung mit erledigen. Ich beginne bei einer normalen Webanwendung fast immer mit dieser Variante und prüfe erst danach speziellere Optionen.
Unique- und Primärschlüssel-Indizes
Ein Unique Index verhindert doppelte Werte und beschleunigt zugleich die Suche. Primärschlüssel und eindeutige Constraints erzeugen in vielen Datenbanksystemen automatisch einen passenden Index. Einen zweiten manuellen Index auf derselben Spalte anzulegen, wäre dann meist nur doppelte Last.
Hash-, Volltext- und räumliche Indizes
Hash-Indizes sind vor allem für exakte Vergleiche interessant, aber weniger flexibel bei Bereichsabfragen. Für eine Suche nach Wörtern in längeren Texten ist ein Full-Text-Index geeigneter als ein gewöhnlicher B-Tree. Geodaten wiederum profitieren von räumlichen Indexstrukturen wie R-Trees. Diese Varianten sind kein allgemeiner Ersatz, sondern Werkzeuge für klar definierte Suchmuster.
Lesen Sie auch: Symfony Messenger richtig einsetzen - Queues, Worker und Retries
Clustered und nonclustered
SQL Server unterscheidet beispielsweise zwischen clustered und nonclustered Indizes. Ein clustered Index bestimmt die physische beziehungsweise logische Ordnung der Zeilenstruktur, während ein nonclustered Index separat auf die Datensätze verweist. PostgreSQL und MySQL verwenden andere Modelle, deshalb sollte man Begriffe aus einem System nicht unkritisch auf ein anderes übertragen.
Wie die richtige Spaltenreihenfolge entsteht
Mehrspaltige Indizes sind besonders nützlich, wenn eine Abfrage regelmäßig nach mehreren Kriterien filtert. Ein Beispiel für eine Bestellübersicht lautet:
SELECT id, created_at, total
FROM orders
WHERE customer_id = 42
ORDER BY created_at DESC
LIMIT 20;Dazu kann dieser Index passen:
CREATE INDEX idx_orders_customer_created
ON orders (customer_id, created_at DESC);Die Reihenfolge ist nicht beliebig. Ein Index auf (customer_id, created_at) unterstützt typischerweise die Suche nach customer_id sowie nach customer_id und created_at. Eine Abfrage, die ausschließlich nach created_at sucht, kann diesen Index dagegen oft nicht effizient nutzen. Dieses Leftmost-Prefix-Prinzip ist einer der häufigsten Punkte, die beim Indexdesign übersehen werden.
Bei der Planung prüfe ich zuerst die häufigsten Filterspalten und danach Sortierung oder Gruppierung. Spalten mit hoher Selektivität, die also viele Datensätze ausschließen, sind oft gute Kandidaten. Eine Spalte mit nur zwei möglichen Werten, etwa is_active, bringt allein dagegen nicht automatisch einen Vorteil.
| Abfragemuster | Oft passende Strategie | Wichtige Einschränkung |
|---|---|---|
| Exakte Suche nach einer ID | B-Tree oder Unique Index | Ein weiterer Index ist bei Primärschlüsseln meist unnötig |
| Filter plus Sortierung | Mehrspaltiger Index | Die Reihenfolge der Spalten entscheidet |
| Suche in langen Texten | Full-Text-Index | Ein gewöhnlicher Index ersetzt keine Textsuche |
| Seltene Teilmenge einer Tabelle | Partieller oder gefilterter Index | Unterstützung ist je nach Datenbank unterschiedlich |
So prüfe ich, ob der Index wirklich hilft
Ein Index sollte nie allein deshalb angelegt werden, weil eine Spalte in einer Abfrage vorkommt. Entscheidend ist der Ausführungsplan. Mit EXPLAIN zeigt die Datenbank, ob sie einen Index Scan, einen Table Scan, einen Seek oder eine andere Zugriffsstrategie plant.
EXPLAIN
SELECT id, created_at
FROM orders
WHERE customer_id = 42
ORDER BY created_at DESC
LIMIT 20;In PostgreSQL liefert EXPLAIN (ANALYZE, BUFFERS) zusätzlich reale Laufzeit- und I/O-Daten. In MySQL ist EXPLAIN ANALYZE hilfreich, während SQL Server den tatsächlichen Ausführungsplan sowie Statistiken zu logischen Lesevorgängen bereitstellt. Bei produktiven Daten sollte man beachten, dass Varianten mit ANALYZE die Abfrage tatsächlich ausführen können.
Wenn der Optimizer den Index nicht verwendet, ist das nicht automatisch ein Fehler. Bei einer kleinen Tabelle kann ein vollständiger Scan schneller sein, weil der Indexzugriff zusätzlichen Aufwand erzeugt. Auch veraltete Statistiken, eine zu geringe Selektivität oder eine Funktion auf der Spalte können die Entscheidung beeinflussen.
Ein klassischer Problemfall sieht so aus:
SELECT *
FROM users
WHERE LOWER(email) = 'anna@example.de';Ein einfacher Index auf email hilft hier möglicherweise nicht, weil die Datenbank zuerst LOWER() berechnen muss. Je nach System kann ein Ausdrucksindex, eine passende normalisierte Spalte oder eine andere Schreibweise der Abfrage die bessere Lösung sein.
Wann ein Index schadet statt zu helfen
Der wichtigste Zielkonflikt ist leicht zu erklären. Ein zusätzlicher Index macht viele Lesevorgänge schneller, muss aber bei jedem INSERT, UPDATE und DELETE gepflegt werden. Bei einer stark schreibenden Anwendung kann eine Sammlung von zehn ähnlich aufgebauten Indizes deshalb mehr schaden als ein fehlender Index.
Indizes benötigen außerdem Speicher und konkurrieren mit Tabellendaten um den Datenbank-Cache. Breite Schlüssel, lange Textspalten und viele zusätzliche Include-Spalten vergrößern diese Strukturen deutlich. Besonders kurze Primärschlüssel können sich lohnen, weil sie in einigen Speichermodellen in sekundären Indizes mitgeführt werden.
Ich entferne einen Index trotzdem nicht blind, nur weil er im Moment wenig genutzt wird. Zuerst prüfe ich die Nutzungsstatistiken über einen ausreichend langen Zeitraum, den Schreibverkehr und saisonale Abfragen. Ein Index, der im Tagesbetrieb selten gebraucht wird, kann für einen Monatsabschluss trotzdem wichtig sein.
- Keine Indizes auf jede Spalte einer Tabelle setzen.
- Nahezu identische mehrspaltige Indizes zusammenfassen, wenn die Abfragen es erlauben.
- Bei häufigen Schreibvorgängen die zusätzliche Latenz und den Speicherverbrauch messen.
- Statistiken nach größeren Datenänderungen aktualisieren, sofern das System dies nicht automatisch erledigt.
- Unbenutzte Indizes erst nach einer kontrollierten Beobachtungsphase entfernen.
Ein praktikabler Ablauf für neue Anwendungen
Ich lege die ersten Indizes nicht anhand von Vermutungen an, sondern aus den wichtigsten Zugriffspfaden der Anwendung. Dazu gehören Login-Abfragen, Listenansichten, Suchfilter, Fremdschlüssel in Joins und typische API-Endpunkte. Eine einfache Liste der meistgenutzten Abfragen ist oft wertvoller als eine abstrakte Indexrichtlinie.
- Abfrage messen und den aktuellen Ausführungsplan speichern.
- Filter, Join-Bedingungen sowie Sortierung und Gruppierung markieren.
- Einen möglichst schmalen Index mit passender Spaltenreihenfolge entwerfen.
- Plan, Laufzeit, gelesene Seiten und Schreibverhalten vor und nach der Änderung vergleichen.
- Die Änderung als versionierte Migration ausrollen und auf Produktionsdaten testen.
Bei einer großen Tabelle darf auch der Aufbau selbst nicht unterschätzt werden. PostgreSQL bietet mit CREATE INDEX CONCURRENTLY eine Option, die die Verfügbarkeit während des Aufbaus verbessern kann, aber länger dauert und eigene Einschränkungen hat. In MySQL und SQL Server hängen Online-Optionen, Sperren und benötigte Editionen von Version und Konfiguration ab.
Für den Anfang reicht meist ein kleiner, gezielter Satz an Indizes. Die beste Strategie ist nicht die mit den meisten Strukturen, sondern die mit dem größten messbaren Effekt pro zusätzlichem Schreib- und Speicheraufwand.
Die beste Indexentscheidung entsteht aus echten Abfragen
Ein SQL-Index ist kein allgemeiner Geschwindigkeitsschalter. Er hilft, wenn Datenbank, Spaltenreihenfolge, Abfragemuster und Statistiken zusammenpassen. Wer nur die Definition kennt, kommt schnell zu vielen unnötigen Strukturen.
Mein pragmatischer Rat lautet deshalb, mit den langsamsten und wichtigsten Abfragen zu beginnen, jede Änderung mit einem Ausführungsplan zu prüfen und die Auswirkungen auf Schreibvorgänge mitzudenken. So wird aus einer theoretischen Optimierung eine belastbare Entscheidung für die Anwendung.