Den richtigen PostgreSQL-Index wählen

PostgreSQL liefert sechs Index-Zugriffsmethoden mit, und CREATE INDEX ohne USING-Klausel gibt Ihnen immer einen B-tree. Diese Voreinstellung ist meistens richtig — in dieser Anleitung geht es darum, die Fälle zu erkennen, in denen sie es nicht ist, und um die drei Index-Features (partiell, Ausdruck, abdeckend), die in der täglichen Arbeit mehr zählen als die Zugriffsmethode.

B-tree: die Voreinstellung aus gutem Grund

Ein B-tree-Index unterstützt Gleichheits- und Bereichsvergleiche (=, <, >, BETWEEN, IN), IS NULL, Präfix-LIKE 'abc%' (mit der richtigen Collation oder text_pattern_ops) und — oft vergessen — Sortierung: ein Index auf (created_at) kann ORDER BY created_at DESC LIMIT 20 erfüllen, ohne irgendetwas zu sortieren.

Bei mehrspaltigen B-trees ist die Spaltenreihenfolge alles. Ein Index auf (customer_id, created_at) ist hervorragend für WHERE customer_id = ? ORDER BY created_at, aber nahezu nutzlos für eine Abfrage, die nur auf created_at filtert. Faustregel: Gleichheitsspalten zuerst, dann die Bereichs- oder Sortierspalte.

Wenn B-tree das falsche Werkzeug ist

IndextypGreifen Sie dazu, wenn …Achten Sie auf
GIN Der Wert ist ein Container und Sie suchen darin: jsonb @> '{"status":"active"}', Array-Überlappung &&, Volltext tsvector @@ tsquery, Trigramm-Suche mit pg_trgm für LIKE '%term%'. Langsamer zu aktualisieren als B-tree; die Indexgröße kann groß sein. Gut für leselastige Daten.
GiST Geometrische Daten, Bereichstypen (tstzrange-Überlappungen), Nächste-Nachbarn-ORDER BY location <-> point und Exclusion-Constraints (z. B. „keine sich überlappenden Buchungen für denselben Raum"). Verlustbehaftet bei manchen Operatorklassen; meist größer und langsamer als ein B-tree bei einfacher Gleichheit.
BRIN Riesige Append-only-Tabellen (Logs, Events), bei denen die Spalte mit der physischen Zeilenreihenfolge korreliert, typischerweise Zeitstempel. Winzig — Megabytes, wo ein B-tree Gigabytes bräuchte. Nutzlos, wenn die Daten nicht physisch nach dieser Spalte geordnet sind; es grenzt den Scan nur auf Blockbereiche ein.
Hash Reine Gleichheit auf langen Werten, bei denen ein B-tree dick wird. Selten nötig; seit PostgreSQL 10 crash-sicher und repliziert. Nur Gleichheit: keine Bereiche, keine Sortierung, keine Durchsetzung von Eindeutigkeit.
SP-GiST Raumpartitionierte Strukturen: Präfixsuchen auf Text, IP-Bereiche (inet), Quadtrees für Punkte. Nische; testen Sie gegen GiST/B-tree für Ihre tatsächlichen Daten, bevor Sie sich festlegen.

Partielle Indexe: indexieren Sie nur, was Sie abfragen

CREATE INDEX orders_pending_idx
    ON orders (created_at)
    WHERE status = 'pending';

Wenn 99 % Ihrer Bestellungen abgeschlossen sind, aber jede Abfrage nach den ausstehenden fragt, bleibt ein partieller Index klein, bleibt heiß im Cache und ist günstig zu pflegen — abgeschlossene Zeilen berühren ihn nie. Die Abfrage muss die WHERE-Bedingung enthalten (oder etwas, von dem der Planer beweisen kann, dass es sie impliziert), damit der Index in Betracht gezogen wird.

Ausdrucks-Indexe: passen Sie zu dem, was die Abfrage berechnet

CREATE INDEX users_email_lower_idx ON users (lower(email));

Eine Abfrage, die auf lower(email) = lower($1) filtert, kann keinen einfachen Index auf email nutzen — der Index speichert Rohwerte, nicht kleingeschriebene. Der Ausdruck im Index muss dem Ausdruck in der Abfrage entsprechen. Dieselbe Technik funktioniert für date_trunc('day', created_at), JSON-Feldextraktion und jede immutable Funktion.

Abdeckende Indexe und Index-Only-Scans

CREATE INDEX orders_cust_idx
    ON orders (customer_id) INCLUDE (total, created_at);

Wenn der Index jede Spalte enthält, die die Abfrage benötigt, kann PostgreSQL die Tabelle vollständig überspringen — ein Index-Only-Scan. INCLUDE fügt den Index-Blättern Nutzlastspalten hinzu, ohne sie zum Teil des Schlüssels zu machen. Zwei Vorbehalte: der Index wird größer, und Index-Only-Scans erfordern außerdem, dass die Visibility Map aktuell ist — bei einer stark aktualisierten, selten vacuumierten Tabelle sehen Sie Heap Fetches steigen und ein Großteil des Vorteils verschwindet.

Praktische Regeln

Der EXPLAIN Visualizer markiert die zwei klassischen Anzeichen eines fehlenden oder falschen Index: sequenzielle Scans, die weit mehr lesen, als sie zurückgeben, und große Rows Removed by Filter-Zahlen.
🧯 Verwandte Fehler: duplicate key value violates unique constraint, no constraint matching ON CONFLICT und Fremdschlüsselverletzungen — langsame Löschungen auf der Elterntabelle bedeuten oft eine nicht indexierte FK-Spalte.