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.