Index-Strategien framework-unabhaengig durchdacht
SELECT
JOIN
SQL · Indexdesign · Datenbank-Performance
Index-Strategien framework-unabhaengig durchdacht
Selektivitaet, Spaltenreihenfolge und Covering Indexes

Eine gute Index-Strategie haengt nicht davon ab, welches ORM oder Framework auf der Datenbank aufsetzt, sondern von grundlegenden Prinzipien wie Selektivitaet, Spaltenreihenfolge und dem gezielten Einsatz von Covering Indexes. Wer diese Prinzipien einmal verstanden hat, trifft bessere Indexentscheidungen, unabhaengig davon, ob die Anwendung in PHP, Java, Python oder direkt in SQL geschrieben ist.

19 Min. Lesezeit Selektivitaet · Composite Index · Covering Index · B-Tree MySQL · PostgreSQL · SQL Server

1. Warum Index-Strategie unabhaengig vom Framework gedacht werden sollte

Viele Entwickler ueberlassen die Index-Strategie stillschweigend dem eingesetzten ORM oder Framework, in der Annahme, automatisch generierte Indizes auf Fremdschluesseln seien bereits ausreichend. Das ist ein Trugschluss. Ein ORM kennt die Struktur der Tabellen, aber nicht die tatsaechlichen Zugriffsmuster einer Anwendung, welche Spalten in WHERE-Klauseln kombiniert werden, in welcher Reihenfolge sortiert wird und wie selektiv einzelne Filter tatsaechlich sind. Eine durchdachte Index-Strategie entsteht aus dem Verstaendnis der Datenstruktur und der Abfragen, nicht aus Framework-Konventionen.

Der Vorteil einer framework-unabhaengigen Index-Strategie liegt darin, dass sie sich auf jede relationale Datenbank uebertragen laesst, ob die Anwendung mit einem PHP-ORM, einem Java-Framework oder direkt mit rohen SQL-Statements arbeitet. Die zugrunde liegenden Konzepte, Selektivitaet, Spaltenreihenfolge, Covering Indexes, sind Eigenschaften des B-Tree-Index selbst und nicht des Frameworks, das die SQL-Anweisungen generiert. Dieser Artikel behandelt genau diese Konzepte, losgeloest von jeder konkreten Technologie.

Ein zweiter Grund fuer diese Betrachtungsweise: Frameworks wechseln, Migrationen zwischen ORMs oder gar zwischen Datenbanksystemen kommen in der Praxis regelmaessig vor. Eine Index-Strategie, die auf grundlegenden Prinzipien statt auf Framework-spezifischen Annotationen beruht, ueberlebt solche Migrationen unveraendert, waehrend automatisch generierte Indizes bei jedem Technologiewechsel neu ueberdacht werden muessen.

2. Wie ein B-Tree-Index tatsaechlich funktioniert

Der Standardindex in nahezu jeder relationalen Datenbank ist ein B-Tree, eine balancierte Baumstruktur, die Werte sortiert haelt und logarithmische Suchzeiten garantiert. Jeder Knoten des Baums enthaelt eine Menge sortierter Schluessel mit Verweisen auf Kindknoten oder, an den Blaettern, auf die eigentlichen Tabellenzeilen beziehungsweise deren Adressen. Diese Sortierung ist der Grund, warum eine gute Index-Strategie nicht nur ueber das Vorhandensein eines Index entscheidet, sondern auch darueber, in welcher Reihenfolge Werte im Index liegen.

Weil ein B-Tree sortiert ist, unterstuetzt er nicht nur Gleichheitsvergleiche effizient, sondern auch Bereichsabfragen wie BETWEEN, weniger-als und die Sortierung per ORDER BY, sofern die Sortierrichtung mit der Indexrichtung uebereinstimmt. Diese Eigenschaft ist zentral fuer jede Index-Strategie, weil ein einziger gut gewaehlter Index gleichzeitig Filterung und Sortierung uebernehmen kann, ohne dass die Datenbank separat sortieren muss.

Ein Hash-Index, den manche Datenbanken alternativ anbieten, ist dagegen ausschliesslich fuer exakte Gleichheitsvergleiche geeignet und unterstuetzt weder Bereichsabfragen noch Sortierung. Fuer die meisten Anwendungsfaelle bleibt der B-Tree deshalb der Standard, und jede Index-Strategie, die in diesem Artikel besprochen wird, geht von einem B-Tree-Index aus, sofern nicht explizit anders erwaehnt.


-- Standard B-Tree index: supports equality, range and sorted access
CREATE INDEX idx_orders_customer_date
ON orders (customer_id, order_date);

-- This single index efficiently serves all three queries below
SELECT * FROM orders WHERE customer_id = 4821;
SELECT * FROM orders WHERE customer_id = 4821 AND order_date >= '2026-01-01';
SELECT * FROM orders WHERE customer_id = 4821 ORDER BY order_date DESC;

3. Selektivitaet messen und richtig interpretieren

Selektivitaet ist das zentrale Mass jeder Index-Strategie: Sie beschreibt, wie viele unterschiedliche Werte eine Spalte im Verhaeltnis zur Gesamtzahl der Zeilen besitzt. Eine Spalte mit hoher Selektivitaet, etwa eine E-Mail-Adresse oder eine Bestellnummer, filtert eine Abfrage auf sehr wenige Zeilen herunter und profitiert stark von einem Index. Eine Spalte mit niedriger Selektivitaet, etwa ein Status-Flag mit nur drei moeglichen Werten, filtert kaum etwas heraus, ein Index darauf bringt oft wenig oder wird vom Optimizer sogar ignoriert.

Die Formel fuer Selektivitaet ist einfach: Anzahl unterschiedlicher Werte geteilt durch Gesamtzahl der Zeilen. Ein Wert nahe 1 bedeutet hohe Selektivitaet, ein Wert nahe 0 bedeutet niedrige Selektivitaet. Eine Index-Strategie, die diese Kennzahl ignoriert, fuehrt haeufig zu Indizes, die zwar existieren, aber vom Optimizer nicht genutzt werden, weil ein Full Table Scan bei niedriger Selektivitaet schlicht guenstiger ist als der zusaetzliche Aufwand der Indexnavigation plus Tabellenzugriff.

Wichtig ist, dass Selektivitaet nicht statisch, sondern kontextabhaengig zu betrachten ist. Ein Status-Flag mit niedriger globaler Selektivitaet kann in Kombination mit einem zweiten Filter, etwa einem Kundennamen, durchaus sinnvoll indiziert werden, wenn die Kombination beider Bedingungen die Ergebnismenge stark einschraenkt. Eine solide Index-Strategie betrachtet deshalb immer die tatsaechlichen Abfragemuster, nicht isolierte Einzelspalten.


-- Measuring selectivity: distinct values / total rows
SELECT
  COUNT(DISTINCT status) AS distinct_values,
  COUNT(*) AS total_rows,
  ROUND(COUNT(DISTINCT status) * 1.0 / COUNT(*), 4) AS selectivity
FROM orders;
-- Result: 3 distinct values / 500000 rows = selectivity 0.000006 -> low, avoid a standalone index

SELECT
  COUNT(DISTINCT email) AS distinct_values,
  COUNT(*) AS total_rows,
  ROUND(COUNT(DISTINCT email) * 1.0 / COUNT(*), 4) AS selectivity
FROM customers;
-- Result: 498000 distinct values / 500000 rows = selectivity 0.996 -> high, index pays off

4. Spaltenreihenfolge im Composite Index bestimmen

Bei einem Composite Index, einem Index ueber mehrere Spalten, entscheidet die Reihenfolge der Spalten massgeblich ueber dessen Nutzbarkeit. Ein Index ueber (a, b, c) kann effizient fuer Abfragen genutzt werden, die a allein, a und b, oder a, b und c filtern, aber nicht fuer Abfragen, die nur b oder nur c filtern, ohne a einzubeziehen. Diese Regel, oft "Leftmost Prefix Rule" genannt, ist eine der wichtigsten Grundlagen jeder Index-Strategie mit mehreren Spalten.

Eine bewaehrte Faustregel fuer die Spaltenreihenfolge lautet: Gleichheitsfilter zuerst, Bereichsfilter danach, Sortierspalten am Ende. Eine Spalte, die immer mit einem exakten Wert verglichen wird, etwa customer_id, gehoert an den Anfang des Index. Eine Spalte, die mit einem Bereich verglichen wird, etwa order_date mit einem BETWEEN, gehoert danach, weil ein Bereichsfilter die Nutzbarkeit weiterer Spalten im Index fuer nachfolgende exakte Vergleiche einschraenkt. Eine Index-Strategie, die diese Reihenfolge ignoriert, verschenkt oft erhebliches Optimierungspotenzial.

Ein haeufiger Fehler ist, die Spaltenreihenfolge nach der Selektivitaet der Einzelspalte statt nach dem tatsaechlichen Filtertyp zu waehlen. Eine hochselektive Spalte, die aber in einem Bereichsfilter verwendet wird, sollte trotzdem nach einer weniger selektiven, aber gleichheitsgefilterten Spalte stehen. Diese Nuance unterscheidet eine wirksame Index-Strategie von einer, die zwar theoretisch korrekt wirkt, in der Praxis aber nicht die erwartete Leistung bringt.


-- Rule: equality columns first, range columns after, sort columns last
-- Query pattern: filter by status (equality), date range, sort by amount
CREATE INDEX idx_orders_status_date_amount
ON orders (status, order_date, total_amount);

-- Fully sargable, uses the index for filter AND sort
SELECT order_id, total_amount
FROM orders
WHERE status = 'shipped'
  AND order_date BETWEEN '2026-01-01' AND '2026-01-31'
ORDER BY total_amount DESC;

-- WRONG order: range column before equality column wastes the composite index
CREATE INDEX idx_orders_date_status_bad
ON orders (order_date, status, total_amount);
-- status can no longer use an efficient equality lookup within the range

5. Covering Indexes gezielt einsetzen

Ein Covering Index enthaelt alle Spalten, die eine Abfrage benoetigt, sowohl in der WHERE-Klausel als auch im SELECT-Teil, sodass die Datenbank nach dem Indexzugriff nicht mehr auf die eigentliche Tabelle zugreifen muss. Dieser zusaetzliche Zugriff, in PostgreSQL als Heap Fetch bezeichnet, kostet bei vielen Treffern erheblich Zeit, weil jede Zeile einzeln von der Tabelle gelesen werden muss, selbst wenn der Index selbst bereits sequenziell und schnell durchsucht wurde. Eine Index-Strategie, die Covering Indexes gezielt fuer haeufige, performancekritische Abfragen einsetzt, kann Antwortzeiten drastisch reduzieren.

In MySQL und PostgreSQL erreicht man einen Covering Index, indem zusaetzliche Spalten am Ende des Index aufgenommen werden, entweder als regulaerer Teil des Index oder, in PostgreSQL, ueber die INCLUDE-Klausel, die Spalten im Index speichert, ohne sie fuer die Sortierung oder Suche zu nutzen. SQL Server bietet dieselbe Funktion ebenfalls ueber INCLUDE. Diese Trennung zwischen Suchspalten und mitgefuehrten Spalten ist ein wichtiges Werkzeug jeder modernen Index-Strategie, weil sie den Index kompakt haelt, waehrend trotzdem alle benoetigten Daten enthalten sind.

Covering Indexes sind kein Allheilmittel, denn jede zusaetzliche Spalte vergroessert den Index und erhoeht damit den Speicherbedarf sowie die Kosten fuer Schreiboperationen. Eine sinnvolle Index-Strategie setzt Covering Indexes gezielt fuer die haeufigsten, performancekritischsten Abfragen ein, nicht pauschal fuer jede Tabelle. Der Zielkonflikt zwischen Leseperformance und Schreibkosten wird im spaeteren Abschnitt zu den Schreibkosten genauer betrachtet.


-- PostgreSQL: INCLUDE keeps extra columns in the index without using them for search
CREATE INDEX idx_orders_covering
ON orders (customer_id, order_date)
INCLUDE (total_amount, status);

-- Fully covered: no heap fetch needed, all columns come from the index
SELECT order_date, total_amount, status
FROM orders
WHERE customer_id = 4821
ORDER BY order_date DESC;

-- MySQL equivalent: append columns directly to the composite index
CREATE INDEX idx_orders_covering
ON orders (customer_id, order_date, total_amount, status);

6. Redundante und ueberfluessige Indizes erkennen

Mit der Zeit sammeln sich in gewachsenen Datenbanken oft redundante Indizes an, etwa ein Index ueber (a) neben einem Index ueber (a, b). Der einspaltige Index ist in diesem Fall meist ueberfluessig, weil der zweispaltige Index dank der Leftmost Prefix Rule bereits alle Abfragen bedient, die auch der einspaltige Index bedient haette. Eine regelmaessige Pruefung auf solche Redundanzen ist ein oft vernachlaessigter, aber wichtiger Teil jeder Index-Strategie.

Ebenso problematisch sind Indizes, die zwar einmal fuer eine bestimmte Abfrage angelegt wurden, deren zugehoerige Abfrage aber laengst nicht mehr existiert, weil sich die Anwendung veraendert hat. Solche verwaisten Indizes kosten weiterhin Speicherplatz und verlangsamen jede Schreiboperation auf der betroffenen Tabelle, ohne jemals fuer eine Leseoperation genutzt zu werden. Die meisten Datenbanken bieten Systemsichten, um ungenutzte Indizes zu identifizieren, etwa pg_stat_user_indexes in PostgreSQL oder sys.dm_db_index_usage_stats in SQL Server.

Eine gute Index-Strategie beinhaltet deshalb einen wiederkehrenden Review-Prozess, bei dem Indexnutzung ueber einen ausreichend langen Zeitraum, mindestens einen vollstaendigen Geschaeftszyklus, beobachtet wird, bevor ein scheinbar ungenutzter Index tatsaechlich entfernt wird. Saisonale Abfragemuster, etwa Jahresabschluss-Reports, wuerden bei einer zu kurzen Beobachtungsperiode faelschlich als irrelevant eingestuft.

7. Die Schreibkosten jedes zusaetzlichen Index

Jeder Index beschleunigt Leseoperationen, verlangsamt aber gleichzeitig jede INSERT-, UPDATE- und DELETE-Operation auf der betroffenen Tabelle, weil die Datenbank den Index bei jeder Aenderung konsistent halten muss. Eine Index-Strategie, die diesen Zielkonflikt ignoriert und blind jede potenziell nuetzliche Spalte indiziert, fuehrt bei schreibintensiven Tabellen zu spuerbaren Performance-Einbussen, oft gerade dort, wo Latenz besonders kritisch ist, etwa bei Bestell- oder Zahlungsprozessen.

Der Schreibkosten-Effekt verstaerkt sich zusaetzlich bei Composite Indexes und Covering Indexes, weil hier mehr Daten pro Indexeintrag geschrieben und bei Aktualisierungen neu sortiert werden muessen. Fuer eine Tabelle mit zehn Indizes bedeutet ein einzelnes INSERT zehn zusaetzliche Schreiboperationen, nicht nur eine. Eine durchdachte Index-Strategie waegt deshalb bei jeder Tabelle das Verhaeltnis von Lese- zu Schreiblast ab und indiziert bei schreiblastigen Tabellen deutlich zurueckhaltender als bei ueberwiegend lesenden Reporting-Tabellen.

Eine praktische Heuristik: Bei Tabellen mit einem Lese-Schreib-Verhaeltnis von mindestens 10 zu 1 lohnt sich in der Regel jeder Index, der eine haeufige Abfrage spuerbar beschleunigt. Bei ausgeglichenem oder schreiblastigem Verhaeltnis sollte jeder zusaetzliche Index einzeln gegen seinen Nutzen gerechtfertigt werden, statt pauschal jede potenziell hilfreiche Spalte zu indizieren.

8. Partial-, Funktions- und Volltextindizes

Neben dem klassischen B-Tree-Index ueber vollstaendige Spalten bieten moderne Datenbanken spezialisierte Indexformen, die eine gezieltere Index-Strategie ermoeglichen. Ein Partial Index, in PostgreSQL nativ unterstuetzt, indiziert nur die Zeilen, die eine bestimmte Bedingung erfuellen, etwa nur aktive Bestellungen statt aller jemals angelegten. Das reduziert die Indexgroesse drastisch und ist besonders wirksam, wenn Abfragen fast immer nur einen kleinen, klar abgrenzbaren Teil der Tabelle betreffen.

Ein Funktionsindex indiziert nicht die Rohspalte, sondern das Ergebnis eines Ausdrucks, etwa LOWER(email) fuer case-insensitive Suchen oder EXTRACT(YEAR FROM order_date) fuer Jahresauswertungen. Ohne einen solchen Index kann die Datenbank den Index auf der Rohspalte nicht nutzen, sobald eine Funktion auf die Spalte angewendet wird, weil die sortierte Reihenfolge des Index nicht mehr mit der sortierten Reihenfolge der Funktionsergebnisse uebereinstimmt. Diese Beobachtung ist einer der haeufigsten Gruende, warum ein scheinbar vorhandener Index in der Praxis nicht genutzt wird.

Fuer Textsuche jenseits einfacher LIKE-Muster bieten PostgreSQL und MySQL Volltextindizes an, die Woerter statt exakter Zeichenketten indizieren und Relevanz-Ranking unterstuetzen. Eine vollstaendige Index-Strategie beruecksichtigt diese spezialisierten Indexformen dort, wo Standard-B-Tree-Indizes an ihre Grenzen stossen, etwa bei Volltextsuche, geografischen Daten oder JSON-Dokumenten mit GIN-Indizes in PostgreSQL.


-- PostgreSQL: partial index, only indexes rows matching the condition
CREATE INDEX idx_orders_active
ON orders (customer_id)
WHERE status IN ('pending', 'processing');

-- Function index for case-insensitive lookups
CREATE INDEX idx_customers_email_lower
ON customers (LOWER(email));

-- This query only benefits from the index if the expression matches exactly
SELECT * FROM customers WHERE LOWER(email) = 'user@example.com';

9. Index-Typen im direkten Vergleich

Die Wahl des richtigen Indextyps haengt stark vom Abfragemuster ab. Die folgende Tabelle vergleicht die wichtigsten Indexformen und ihre typischen Einsatzgebiete als schnelle Referenz fuer die eigene Index-Strategie.

Indextyp Staerke Grenze Typischer Einsatz
B-Tree (Standard) Gleichheit, Bereich, Sortierung Groesser bei vielen Spalten Fast alle WHERE- und ORDER-BY-Faelle
Hash Sehr schnelle Gleichheit Kein Bereich, keine Sortierung Reine Key-Value-Lookups
Partial Index Klein, sehr selektiv Nur fuer die definierte Bedingung nutzbar Nur aktive oder unerledigte Zeilen
Funktionsindex Indiziert Ausdrucksergebnis Ausdruck muss exakt uebereinstimmen Case-insensitive Suche, Datumsteile
Volltext / GIN Wortsuche, Relevanz-Ranking Groesser, komplexere Pflege Textsuche, JSON-Dokumente

Diese Uebersicht zeigt, dass eine vollstaendige Index-Strategie selten mit einem einzigen Indextyp auskommt. Der Standard-B-Tree deckt die grosse Mehrheit der Faelle ab, waehrend spezialisierte Indexformen gezielt dort eingesetzt werden, wo B-Tree-Indizes strukturell an ihre Grenzen stossen.

10. Zusammenfassung

Eine wirksame Index-Strategie basiert auf Prinzipien, die unabhaengig vom eingesetzten Framework oder ORM gueltig bleiben: hohe Selektivitaet bevorzugen, im Composite Index Gleichheitsspalten vor Bereichsspalten anordnen, Covering Indexes fuer performancekritische Abfragen gezielt einsetzen und die Schreibkosten jedes zusaetzlichen Index bewusst gegen seinen Lesevorteil abwaegen. Diese Prinzipien gelten fuer MySQL genauso wie fuer PostgreSQL oder SQL Server, weil sie Eigenschaften des B-Tree-Index selbst sind.

Der groesste Fehler in der Praxis ist, Indexentscheidungen komplett dem ORM zu ueberlassen oder Indizes rein reaktiv nach beobachteten Performance-Problemen anzulegen. Eine durchdachte Index-Strategie entsteht proaktiv aus dem Verstaendnis der tatsaechlichen Abfragemuster einer Anwendung, kombiniert mit regelmaessiger Pruefung auf Redundanz und ungenutzte Indizes. Dieses Wissen bleibt auch dann wertvoll, wenn sich das Framework, die Programmiersprache oder sogar das Datenbanksystem in Zukunft aendert.

Index-Strategien framework-unabhaengig, das Wichtigste auf einen Blick

Selektivitaet zuerst

Hochselektive Spalten profitieren am meisten von einem Index, niedrigselektive selten.

Spaltenreihenfolge

Gleichheitsfilter zuerst, Bereichsfilter danach, Sortierspalten am Ende des Composite Index.

Covering Indexes

Alle benoetigten Spalten im Index vermeiden den zusaetzlichen Tabellenzugriff komplett.

Schreibkosten beachten

Jeder Index verlangsamt Schreiboperationen, deshalb Lese-Schreib-Verhaeltnis pro Tabelle beruecksichtigen.

11. FAQ: Index-Strategien framework-unabhaengig

1Was bedeutet framework-unabhaengig?
Eine Strategie basierend auf Selektivitaet und Spaltenreihenfolge statt automatisch generierter ORM-Indizes.
2Was ist Selektivitaet?
Verhaeltnis unterschiedlicher Werte zur Gesamtzeilenzahl. Hohe Selektivitaet profitiert stark von einem Index.
3Spaltenreihenfolge im Composite Index?
Gleichheit zuerst, Bereich danach, Sortierspalten am Ende. Maximiert Nutzbarkeit fuer Filter und Sortierung.
4Was ist ein Covering Index?
Enthaelt alle benoetigten Spalten, sodass kein Zugriff auf die Tabelle noetig ist. Reduziert Antwortzeit deutlich.
5Warum nicht jede Spalte indizieren?
Jeder Index verlangsamt Schreiboperationen, weil er bei jeder Aenderung konsistent gehalten werden muss.
6Was ist die Leftmost Prefix Rule?
Ein Composite Index (a, b, c) nutzt nur Praefixe: a, a+b oder a+b+c, niemals b oder c allein ohne a.
7Wann lohnt sich ein Partial Index?
Wenn Abfragen meist nur einen kleinen, klar abgrenzbaren Teil der Tabelle betreffen, etwa aktive Zeilen.
8Warum wird mein Index bei Funktionen ignoriert?
Der Index ist auf der Rohspalte sortiert, nicht auf dem Funktionsergebnis. Dafuer braucht es einen eigenen Funktionsindex.
9Wie finde ich ungenutzte Indizes?
Ueber Systemsichten wie pg_stat_user_indexes, beobachtet ueber einen langen Zeitraum inklusive saisonaler Muster.
10Gilt die Strategie fuer jede Datenbank gleich?
Grundprinzipien gelten universell fuer B-Tree-Indizes, konkrete Syntax und Zusatzfeatures unterscheiden sich.