Hash-Indizes in PostgreSQL
PostgreSQL, Teil 1
Wenn du – so wie ich – aus der SQL-Server-Welt kommst, hast du bereits ein ausgeprägtes Gespür für Indexierung. Du weißt, warum B-Trees dominieren, warum sortierte Strukturen wichtig sind und warum Gleichheitsabfragen in der Regel auch ohne Spezialmechanismen schnell genug sind. Wenn du dann zum ersten Mal Hash-Indizes in PostgreSQL siehst, lautet die naheliegende Frage nicht, wie sie funktionieren – sondern warum es sie überhaupt gibt.
Schließlich hat SQL Server für seine klassische, festplattenbasierte Engine niemals einen separaten Hash-Indextyp benötigt. Und wenn du bereits mit In-Memory OLTP gearbeitet hast, verbindest du Hash-Indizes vermutlich mit etwas ganz anderem – und weit Spezialisierterem.
Dieser Artikel soll helfen, ein passendes mentales Modell zu entwickeln – eines, das PostgreSQL-Hash-Indizes sinnvoll in Beziehung zu deinem vorhandenen Wissen setzt, ohne sie mit den In-Memory-Technologien des SQL Server zu verwechseln.
Die gemeinsame Basis: Warum B-Trees überall dominieren
Sowohl SQL Server als auch PostgreSQL verwenden B-Trees als Standardindexstruktur – und das aus gutem Grund: Sie sind extrem flexibel. Gleichheitsabfragen, Bereichssuchen, sortierte Ausgaben, Präfix-Suche, Merge-Joins – all das ergibt sich ganz natürlich aus sortierten Schlüsseln.
Deshalb ist der B-Tree auch in PostgreSQL – genau wie in SQL Server – der „Allzweck-Index“. Er ist selten die absolut schnellste Struktur für eine ganz bestimmte Operation, aber er ist über nahezu alle Abfrageformen hinweg konstant gut. Diese Universalität bildet den Kontext, in dem Hash-Indizes richtig verstanden werden müssen.
Hash-Indizes existieren nicht, weil B-Trees unzureichend wären. Sie existieren, weil die zusätzlichen Fähigkeiten eines B-Trees in manchen Szenarien schlicht unnötiger Overhead sind.
Was ein PostgreSQL-Hash-Index wirklich ist
Ein PostgreSQL-Hash-Index verzichtet bewusst auf Ordnung. Statt Schlüsselwerte sortiert zu halten, wird eine Hashfunktion auf den zu indizierenden Wert angewendet. Das Ergebnis bestimmt dann den Bucket, in dem passende Einträge liegen.
Für SQL-Server-Profis ist dies ein wichtiger Perspektivenwechsel: Ein Hash-Index in PostgreSQL ist keine allgemeine Indexstrategie. Er ist eine explizite Vereinbarung zwischen Schema-Designer und Query-Planner.
Solange diese Vereinbarung eingehalten wird – also nur strikte Gleichheitsabfragen stattfinden –, kann der Index weniger Arbeit verrichten. Wird sie verletzt, ist der Index wertlos.
Gleichheit ohne Ordnung – warum das trotzdem relevant sein kann
Auf den ersten Blick wirkt das wie eine akademische Unterscheidung. B-Trees verarbeiten Gleichheitsabfragen sowohl in SQL Server als auch in PostgreSQL bereits sehr effizient. In den meisten Systemen sind Gleichheitsabfragen so günstig, dass eine weitere Optimierung kaum ins Gewicht fällt.
Der Unterschied zeigt sich erst in sehr repetitiven, eng fokussierten Workloads. Ein Hash-Index vermeidet den Aufwand, beim Einfügen und Aktualisieren Ordnung aufrechtzuerhalten. Er muss sich nicht um Nachbarschlüssel oder Baumstruktur kümmern. Unter passenden Bedingungen führt diese Einfachheit zu etwas geringerem Pflegeaufwand und sehr direkten Zugriffspfaden.
Erstellen und Verwenden eines Hash-Index in PostgreSQL
Machen wir es konkret: Stellen wir uns eine Tabelle vor, die wie ein Key-Value-Store funktioniert. Beispielsweise eine Session-Tabelle, in der jede Abfrage über einen exakten Session-Token läuft.
-- Create a simple table CREATE TABLE Sessions ( SessionID TEXT PRIMARY KEY, UserID TEXT NOT NULL, CreatedTimestamp TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Insert 10mio records INSERT INTO Sessions (SessionID, UserID) SELECT 'Session_' || s, 'User' || s || '@sqlpassion.at' FROM generate_series(1, 10000000) s;
Wenn der Workload fast ausschließlich aus Abfragen wie der folgenden besteht, spielt Ordnung keine Rolle:
SELECT UserID FROM Sessions WHERE SessionID = '424242';
Es wird niemals einen Range-Predicate, kein ORDER BY SessionID und keine Präfixsuche geben. In diesem Fall kannst du explizit einen Hash-Index anlegen:
-- Create a Hash Index CREATE INDEX idx_SessionID ON Sessions USING hash (SessionID);
PostgreSQL kann diesen Index dann für Gleichheitsabfragen nutzen. Ein EXPLAIN-Plan zeigt einen Index-Scan über den Hash-Index, wenn der Query-Planner ihn als günstigsten Pfad ansieht (Bild 1).
Der Index-Scan über den Hash-Index (Bild 1)
AutorWichtig ist dabei weniger die Syntax, sondern die Absicht. Mit einem Hash-Index erklärst du PostgreSQL ausdrücklich: Diese Spalte existiert ausschließlich für exakte Vergleiche. Wenn sich diese Annahme ändert, musst du die Indexstrategie neu bewerten.
Warum der Query-Planner mit Hash-Indizes vorsichtig ist
Der PostgreSQL-Planner bevorzugt Hash-Indizes nicht aggressiv. Selbst wenn ein solcher vorhanden ist, kann er dennoch einen B-Tree auswählen, wenn dieser ähnliche Kosten bei größerer Flexibilität bietet. Das entspricht der Philosophie von SQL Server: Optimierer bevorzugen Strukturen, die Optionen offenhalten. Ein Hash-Index ist nur dann attraktiv, wenn seine Einschränkungen für die Abfrage irrelevant sind.
Das führt dazu, dass viele PostgreSQL-Systeme Hash-Indizes enthalten, die formal korrekt sind, praktisch aber kaum genutzt werden. Das ist kein Fehler der Engine, sondern ein Zeichen dafür, dass der Workload die Spezialisierung nicht wirklich rechtfertigt.
Wo Hash-Indizes wirklich passen
Hash-Indizes sind in PostgreSQL sinnvoll, wenn das Zugriffsmuster eher einer Lookup-Tabelle gleicht als einer relationalen Struktur. Typische Beispiele sind Session-Stores, Token-Tabellen, exakte Joins über Surrogate Keys oder Faktentabellen mit rein identifikationsbasierter Zugriffslogik.
In all diesen Fällen verhält sich die Spalte eher wie ein Wörterbuchschlüssel – nicht wie ein sortierbares Attribut. Solange dieses mentale Modell gilt, passt Hashing gut. Sobald Ordnung, Gruppierung oder Range-Filterung ins Spiel kommen, bricht es zusammen – und damit der Nutzen des Hash-Index.
Hash-Indizes versus SQL Server In-Memory OLTP
Damit sind wir beim häufigsten – und gefährlichsten – Missverständnis. SQL-Server-In-Memory-OLTP-Hash-Indizes gehören zu einer völlig anderen Welt. Sie laufen in einer spezialisierten Engine, benötigen explizite Bucket-Größen und sind für latenzfreien, speicherresidenten Betrieb optimiert. Ihre Wahl ist eine architektonische Grundsatzentscheidung.
PostgreSQL-Hash-Indizes sind etwas völlig anderes. SQL-Server-In-Memory-Hash-Indizes sind in ihrer Ausgestaltung vergleichbar mit einem Triathlon-Zeitfahrrad (Bild 2): aggressive Geometrie, tiefe Carbon-Felgen, exakt abgestimmte Kassette – perfekt, solange die Strecke bekannt ist. Wenn sich das Profil ändert, leidet sofort die Performance.
Ein typisches Triathlon-Bike (Bild 2)
AutorPostgreSQL-Hash-Indizes sind dagegen eher wie der Austausch der Kassette am Rennrad für ein sehr flaches Rennen. Du änderst nicht das Rad, nicht den Rahmen, nicht die Disziplin – nur die Übersetzung, weil die Strecke vorhersehbar und gleichförmig ist.
Der Unterschied ist entscheidend. Das eine ist eine Engine-Entscheidung, das andere eine taktische Feinjustierung.
Zusammenfassung
Für SQL-Server-Profis auf dem Weg zu PostgreSQL gilt: Behandle Hash-Indizes als bewusste Ausnahme. Sie sind keine Performance-Tricks. Sie sind keine Entsprechung zu In-Memory-OLTP-Mechanismen. Sie sind einfache Werkzeuge für einfache Zusagen: „Diese Spalte wird ausschließlich für exakte Gleichheit verwendet.“
Wenn dieses Versprechen gilt, kann ein Hash-Index elegant sein. Wenn nicht, bleibt der B-Tree weiterhin die richtige Antwort. In PostgreSQL. Und im Leben.