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
| Indextyp | Greifen 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
- Erstellen Sie Indexe in der Produktion mit
CREATE INDEX CONCURRENTLY— es blockiert keine Schreibvorgänge (es kann nicht innerhalb einer Transaktion laufen und dauert länger, das ist der Preis). - Jeder Index belastet jedes
INSERT/UPDATE/DELETE. Prüfen Sie regelmäßigpg_stat_user_indexes.idx_scanund verwerfen Sie, was nie gelesen wird. - Ein Index, den der Planer ignoriert, hat nicht zwangsläufig fehlende Statistiken — prüfen Sie, ob die Typen passen, ob Funktionen im Prädikat dem Indexausdruck entsprechen und ob der gefilterte Anteil klein genug ist, um einen sequenziellen Scan zu schlagen.
- Verifizieren, nicht annehmen: führen Sie
EXPLAIN (ANALYZE, BUFFERS)davor und danach aus. Der Beweis steckt im Plan.
Rows Removed by Filter-Zahlen.
