# Indizes in PostgreSQL - Ein Überblick
Stefan Keller, 2022, aktualisiert Dezember 2025
## Einleitung
Indizes sind eine gängige Methode zur Verbesserung der
Datenbank-Performanz. Ein Index ermöglicht es dem Datenbankserver,
bestimmte Zeilen schneller zu finden und abzurufen, als dies ohne einen
Index möglich wäre. Indizes verursachen jedoch auch einen zusätzlichen
Aufwand für das gesamte Datenbanksystem, weshalb sie sinnvoll eingesetzt
werden sollten.
Die PostgreSQL-Standard-Installation enthält bereits mehrere
Indexmethoden (B-Tree, Hash, GiST, SP-GiST, GIN, BRIN); der Default ist
B-Tree. Weitere Indizes können als Erweiterungen mit "CREATE
EXTENSION" installiert werden. Wieder andere Indexmethoden müssen
zuerst von separaten Repositories heruntergeladen und installiert
werden. Experimentelle Indexe schliesslich verlangen oft, dass man sie
selber kompiliert.
Dieses Dokument ist eine Zusammenstellung von in PostgreSQL verfügbaren
Indizes und ihren Eigenschaften. Die Idee des Dokuments ist, dass man
sich das Anwendungsfeld (Query-Typ) und die Datentypen anschaut und mit
dem eigenen Schema vergleicht, um dann Hinweise zu erhalten, welche
Indizes in Frage kommen.
Die gewählten Indizes können parametrisiert werden (mit kombinierten
Attributen, mit Ausdrücken, mit zusätzlichen "Operator Classes" etc.),
wie in der PostgreSQL-Dokumentation zu "Index Types" beschrieben.
## Eigenschaften von Indizes
Ein Index kann aufgrund seines Algorithmus einer Kategorie zugeordnet
werden und aufgrund seiner Implementierung einem Anwendungsfeld
(Query-Typ, Datentyp).
Folgende Query-Typen werden u.a. unterschieden: Equality-Query,
Range-Query, Point-Query.
## Nutzung von Indizes (Syntax)
Die allgemeine Syntax zur Erstellung eines Index lautet:
```sql
CREATE INDEX [index_name] ON table_name USING index_method (column_name [operator_class]);
```
### Beispiel 1: Einfachste Syntax mit B-Tree-Defaults
Die einfachste Form erstellt einen B-Tree-Index (Standard) ohne explizite Angabe der Index-Methode:
```sql
CREATE INDEX ON users (email);
```
PostgreSQL generiert automatisch einen Index-Namen (z.B. `users_email_idx`) und verwendet B-Tree als Standard-Index-Methode.
### Beispiel 2: Syntax mit explizitem Index-Namen
Bei wichtigen oder komplexen Indizes empfiehlt sich die explizite Angabe eines aussagekräftigen Index-Namens:
```sql
CREATE INDEX idx_users_created_at ON users USING btree (created_at);
```
Hier wird explizit B-Tree als Index-Methode angegeben, was bei B-Tree jedoch optional ist.
### Beispiel 3: Syntax mit Operator-Klasse für JSONB-Datentypen
Bei JSONB-Datentypen können verschiedene Operator-Klassen verwendet werden, abhängig von den Query-Anforderungen:
```sql
CREATE INDEX idx_events_data ON events USING gin (data);
```
Dieser Index verwendet die Standard-Operator-Klasse für JSONB und unterstützt alle JSONB-Operatoren (`@>`, `?`, `?&`, `?|`). Für spezialisierte Enthält-Queries (`@>`) ist die `jsonb_path_ops` Operator-Klasse effizienter:
```sql
CREATE INDEX idx_events_data_path ON events USING gin (data jsonb_path_ops);
```
Dieser Index ist ca. 30% kleiner, unterstützt aber nur den `@>` Operator. Für Queries auf spezifische JSON-Pfade können Expression-Indizes erstellt werden:
```sql
CREATE INDEX idx_events_user_id ON events USING btree ((data->>'user_id'));
```
Hier wird ein B-Tree-Index auf den extrahierten `user_id`-Wert aus dem JSON-Dokument erstellt, nützlich für `WHERE data->>'user_id' = '123'` Queries.
Gruppierung der Indizes von PostgreSQL in Kategorien (siehe
PostgreSQL-Dokumentation "Index Types"):
- Baumindizes: B-Tree (built-in), GiST (Generalized Search Tree,
built-in), SP-GiST (built-in)
- Hash-Indizes: Hash (built-in, seit PG 10 WAL-logged und crash-safe)
- Inverted Indizes: GIN (Generalized Inverted Index, built-in), RUM
(experimentell)
- Block-Range-Indizes: BRIN (Block Range Index, built-in)
- Spezialisierte / probabilistische Indizes: Bloom-Index
(Bloom-Filter, Extension), Vektor-Indizes (pgvector: IVFFlat, HNSW,
Extension), weitere Machine-Learning-Indizes (experimentell,
Extension)
## Indizes-Eigenschaften in tabellarischer Übersicht
| Name | Kategorie | Anwendungsfeld | Erstellzeit | Diskplatz | Operatoren (Op.) |
|------|-----------|----------------|-------------|-----------|------------------|
| B-Tree | (balancierter) Baumindex | Equality-Queries, Range-Queries, Sortierung, Pattern-Matching (mit passenden Operator-Klassen). Unterstützt INCLUDE-Klausel für Covering Indexes (seit PG 11). | mässig | mittel | Equality, Vergleichsoperatoren, Pattern-Matching. |
| GiST | (balancierter) Baumindex | Neuere Datentypen wie Geometrie, Text, binäre Datentypen, Bereiche, Ähnlichkeitssuche. | mässig bis langsam | mittel bis gross | Verschiedene. Framework zur Entwicklung eigener Indizes. |
| SP-GiST | (nicht balancierter, space-partitioned) Baumindex | Alternative zu GiST für hierarchisch/raumbezogen partitionierbare Daten (z.B. IP-Präfixe, Quadtrees, Tries). | mässig bis langsam | mittel | Verschiedene. |
| Hash | Hash | Equality- / Point-Queries auf einer Spalte. Seit PG 10 WAL-logged und produktionsreif. | schnell bis mässig | klein bis mittel | Nur Equality-Operator, keine Range-Queries. |
| GIN | Inverted Index | Für Text-Datentypen. Abfragen nach Werten innerhalb zusammengesetzter Datentypen wie hstore, array, JSONB, tsvector oder range-Datentypen. | langsam | gross | Spezielle Op.-Klassen für Arrays, JSONB/JSONPath (inkl. jsonb_path_ops), tsvector, sowie weitere; Framework zur Entwicklung eigener Indizes. |
| RUM | Inverted Index (Extension) | Optimierung des GIN für Text-Datentypen und für Queries mit zusammengesetzten Attributen; Relevanz-Ranking. | langsam bis sehr langsam | gross bis sehr gross | Spezielle Op. ergänzend zu den Op. von GIN für Datentypen Arrays und tsvector/tsquery. |
| BRIN | Block-Range-Index | Platzsparend bei grossen Datenmengen (>1M Tupel), natürlich-sequenzielle Daten (v.a. Zeit, ID). Gut bei INSERTs und Queries mit grossen Ergebnismengen. Verschiedene Op.-Klassen: minmax, inclusion, bloom (seit PG 14). | sehr schnell | sehr klein | Vergleichsoperatoren; Min/Max- und Inklusionsoperatoren (abhängig vom Datentyp und Op.-Klasse). |
| Bloom* | Bloom-Filter-Index (Extension) | Platzsparend und performant für das Prüfen, ob eine Kombination von Werten existiert. Geeignet für Tabellen mit vielen Attributen. | schnell | klein | Equality-Operatoren und Operator-Klassen für Datentypen int4 und text. |
| pgvector (IVFFlat/HNSW) | Vektorindex (ANN, Extension) | Vektor-Embeddings, Ähnlichkeitssuche (k-nächste Nachbarn), KI-Anwendungen (z.B. RAG, Recommendations). Bei kleinen Datensätzen (<10k Vektoren) oft sequentieller Scan ausreichend. | mässig bis langsam (HNSW tendenziell langsamer im Aufbau, aber schneller bei Queries) | mittel bis gross | Distanz-/Ähnlichkeitsoperatoren (<->, <#>, <=>) mit Operator-Klassen vector_l2_ops, vector_ip_ops, vector_cosine_ops. |
\* Bloom-Indizes sind als contrib-Modul verfügbar und werden mit
"CREATE EXTENSION bloom;" installiert. pgvector-Indizes werden über
die Extension "vector" bereitgestellt ("CREATE EXTENSION vector;").
## Spezielle Index-Varianten
### Covering Indexes (INCLUDE-Klausel)
Seit PostgreSQL 11 unterstützen B-Tree-Indizes die INCLUDE-Klausel, mit der zusätzliche Spalten im Index gespeichert werden können, ohne dass sie Teil des Index-Keys sind. Dies ermöglicht Index-Only-Scans für Queries, die auch auf nicht-indexierte Spalten zugreifen.
**Syntax:** `CREATE INDEX idx ON table (indexed_col) INCLUDE (additional_col);`
**Use Case:** E-Commerce-Produkttabelle mit Index auf `category_id` und INCLUDE `(price, name)` ermöglicht schnelle Kategorieabfragen ohne Tabellenzugriff.
### Partielle Indizes (Partial Indexes)
Ein partieller Index enthält nur Zeilen, die eine WHERE-Bedingung erfüllen. Dies reduziert die Indexgrösse und verbessert die Performance für Queries auf dieser Teilmenge.
**Syntax:** `CREATE INDEX idx ON table (col) WHERE condition;`
**Use Case:** Index auf `status = 'active'` in einer Benutzertabelle, wo 95% der Nutzer inaktiv sind und Queries fast immer nur aktive Nutzer betreffen.
### Expression Indexes (Funktionsindizes)
Expression Indexes indexieren das Ergebnis einer Funktion oder eines Ausdrucks statt direkt eine Spalte. Sie sind nützlich für häufig verwendete Transformationen.
**Syntax:** `CREATE INDEX idx ON table (LOWER(email));` oder `CREATE INDEX idx ON table (EXTRACT(YEAR FROM created_at));`
**Use Case:** Case-insensitive Suche in E-Mail-Adressen oder Filterung nach Jahr in Datumsfeldern ohne wiederholte Funktionsaufrufe in jeder Query.
## Datentypen mit passenden Indizes
Es gibt folgende Gruppen von Datentypen in PostgreSQL:
- Numerische Datentypen
- Zeichenketten-Datentypen (text, varchar, tsvector/tsquery)
- Binäre Datentypen (bytea)
- Date/Time-Datentypen
- Boolean-Datentypen
- Aufzähl-Datentypen
- Arrays
- Währungs-Datentypen (MONEY, NUMERIC)
- Key-Value-Datentypen (hstore)
- Dokument-Datentypen (JSON/JSONB/JSONPath)
- Raumbezogene Datentypen (PostGIS geometry/geography)
- Embedding-Datentypen (Vektoren, z.B. vector mittels
pgvector-Extension)
### 1. Numerische Datentypen
Integer, Floating-Point Numbers => B-Tree (Standard), Hash (nur Equality), BRIN (für grosse, natürlich sortierte Tabellen).
**Use Case:** BRIN-Index auf `order_id` in append-only Order-Tabelle mit Milliarden Zeilen spart 99% Speicherplatz gegenüber B-Tree.
### 2. Zeichenketten-Datentypen
Char, Text, Varchar => B-Tree (Equality, Sortierung), GIN/GiST (mit pg_trgm für Fuzzy-Suche und LIKE-Queries mit '%pattern%'), Bloom (Multi-Spalten-Gleichheit).
**Use Case:** GIN-Index mit `gin_trgm_ops` für Produktnamen ermöglicht schnelles `WHERE name ILIKE '%such%'` auch bei Millionen Produkten.
Tsvector, Tsquery => GIN (Standard, schnelle Suche), RUM (Relevanz-Ranking mit Positionsinformationen), GiST (Alternative, besser bei häufigen Updates).
**Use Case:** GIN-Index auf tsvector für Volltextsuche in Dokumenten-Datenbank mit Millionen Artikeln.
### 3. Binäre Datentypen
Bytea. Direkte Indizierung nur eingeschränkt sinnvoll (Grössen-Limit der Indexeinträge). Typisch: Ausdrucksindex auf Hash (z.B. `md5(bytea)` oder `sha256(bytea)`) => B-Tree oder Hash.
**Use Case:** Expression-Index `CREATE INDEX ON files (md5(content))` für Deduplizierung von Datei-Uploads.
### 4. Date/Time-Datentypen
Date, Time, Timestamp, Timestamptz, Interval => B-Tree (Standard, für Range-Queries), BRIN (grosse Zeitreihen-Tabellen mit chronologischer Einfügereihenfolge), Hash (nur Equality, selten nötig).
**Use Case:** BRIN-Index auf `created_at` in IoT-Sensor-Tabelle mit append-only Log-Daten.
### 5. Boolean-Datentypen
Boolean. Eigener Index meist nicht sinnvoll (nur zwei Werte, geringe Selektivität). Eher: partieller Index, z.B. `WHERE active = true`, wenn eine Ausprägung selten ist.
**Use Case:** Partieller Index `WHERE deleted = false` in Soft-Delete-Tabelle, wo 98% der Zeilen nicht gelöscht sind.
### 6. Aufzähl-Datentypen
Benutzerdefinierter Typ mit `CREATE TYPE name AS ENUM ('value1', 'value2')`. => B-Tree (Standard), oft mit partiellem Index auf seltenen Enum-Werten.
**Use Case:** Partieller Index `WHERE order_status = 'pending'` bei Order-Status-Enum, wo die meisten Orders 'completed' oder 'cancelled' sind.
### 7. Arrays
Array => GIN (Enthält-Abfragen mit `@>`, `<@`, `&&` Operatoren für Überschneidung), B-Tree (Gleichheit des ganzen Arrays, selten verwendet).
**Use Case:** GIN-Index auf `tags[]` in Blog-Post-Tabelle für schnelles `WHERE tags @> ARRAY['postgresql', 'performance']`.
### 8. Währungs-Datentypen
PostgreSQL kennt einen eigenen Datentyp MONEY, doch empfohlen wird z.B. NUMERIC(10,2), oder aber BIGINT (in Rappen/Cent) => B-Tree.
**Use Case:** B-Tree-Index auf `price` (als NUMERIC) für E-Commerce-Preisfilter mit Range-Queries.
### 9. Key-Value-Datentypen
hstore => GIN (Standard für Enthält-Queries mit `?`, `@>` Operatoren), GiST (Alternative bei sehr häufigen Updates).
**Use Case:** GIN-Index auf hstore-Spalte `attributes` für Produkt-Metadaten mit dynamischen Schlüsseln.
### 10. Dokument-Datentypen
JSON, JSONB, JSONPath. JSONB => GIN (Standard-Op.-Klasse für allgemeine Enthält-Queries, oder `jsonb_path_ops` für `@>` Operator - 30% kleiner aber nur ein Operator), B-Tree (Gleichheit des ganzen Dokuments), Expression-Index auf spezifische Pfade (z.B. `CREATE INDEX ON table ((data->>'email'))`), BRIN (zusätzlich auf Zeit/ID-Spalte), Bloom (Multi-Spalten-Filtern).
**Use Case:** GIN-Index mit `jsonb_path_ops` auf `metadata` für API-Response-Cache mit `WHERE metadata @> '{"user_id": 123}'`.
JSON => meist vorher in strukturierte Spalten oder JSONB transformieren, da JSON als Text gespeichert wird und nicht effizient indexierbar ist.
### 11. Raumbezogene Datentypen
Die in PostgreSQL mitgelieferten Geometrie-Typen (Point, Line, Lseg, Box, Polygon, Circle, Path) werden selten verwendet. De-facto-Standard ist die PostGIS-Extension.
Geometry, Geography (PostGIS) => GiST (Standard, R-Tree-ähnlich für räumliche Operatoren wie `ST_Intersects`, `ST_Contains`), SP-GiST (alternative räumliche Indexvarianten wie Quad-Tree).
**Use Case:** GiST-Index auf `location` (PostGIS geometry) für Umkreissuche in Restaurant-App mit `ST_DWithin(location, :point, :radius)`.
### 12. Embedding-Datentypen (Vektoren)
vector(n) (pgvector-Extension). Vektor-Embeddings (Text, Bild, Audio) => pgvector-Indizes (IVFFlat - schnellerer Aufbau, HNSW - bessere Query-Performance aber langsamer im Aufbau und mehr Speicher). Kleine Tabellen (<10k Vektoren) => sequentielle Suche mit `ORDER BY embedding <-> :query LIMIT k` oft ausreichend schnell ohne Index.
**Use Case:** HNSW-Index auf `text_embedding` für semantische Suche in Dokumenten-Datenbank (RAG-System) mit Millionen Embeddings.
## Third-Party Index-Erweiterungen (Extensions)
Neben den im Core vorhandenen Indexmethoden (B-Tree, Hash, GiST, GIN,
SP-GiST, BRIN) stellt PostgreSQL zahlreiche Erweiterungen bereit, die
über "CREATE EXTENSION" aktiviert werden. Im Folgenden einige wichtige
Index-nahe Extensions.
### Bloom-Index (Extension "bloom")
Die Extension "bloom" implementiert Bloom-Filter-basierte Indizes für
effiziente Multi-Spalten-Filter mit Gleichheitsbedingungen. Sie ist
besonders geeignet für Tabellen mit vielen Attributen und wechselnden
Abfragemustern, wo traditionelle Multi-Column-B-Tree-Indizes zu gross oder zu spezifisch wären.
**Use Case:** Bloom-Index auf 10 Produkt-Attributen in E-Commerce-Katalog, wo Nutzer beliebige Kombinationen filtern.
### pgvector mit ANN-Indextypen (IVFFlat, HNSW)
Die Extension "vector" (pgvector) erweitert PostgreSQL um den Datentyp
vector(n) für Embeddings sowie approximate-nearest-neighbor-Indizes
IVFFlat und HNSW. HNSW bietet sehr geringe Query-Latenz (oft 10-100x schneller als IVFFlat) bei höherem
Speicherverbrauch und tendenziell längerer Aufbauzeit als IVFFlat. Beide
Indextypen unterstützen Distanz-Operatoren wie `<->` (L2-Distanz), `<#>`
(Inner Product) oder `<=>` (Cosine-Distanz). HNSW wurde später hinzugefügt und ist seit pgvector 0.5.0 verfügbar.
**Use Case:** HNSW-Index für k-nearest-neighbor-Suche in Bild-Embeddings einer Foto-Sharing-Plattform mit Sub-Millisekunden-Latenz.
### Weitere spezialisierte Index-Erweiterungen
#### pg_trgm für Textsuche
Extension "pg_trgm" für trigramm-basierte Textähnlichkeit und Pattern-Matching. Typisch in Kombination mit
GIN- oder GiST-Indizes (`gin_trgm_ops`, `gist_trgm_ops`). Ermöglicht effiziente LIKE-Queries mit führenden Wildcards (`%pattern%`) und Fuzzy-Matching.
**Syntax:** `CREATE EXTENSION pg_trgm; CREATE INDEX idx ON table USING GIN (col gin_trgm_ops);`
**Use Case:** Autovervollständigung in Suchleiste mit `WHERE title ILIKE '%eingabe%'` auf Millionen Artikeln.
#### HypoPG für Index-Analyse
Extension "hypopg" für hypothetische (virtuelle) Indizes. Damit lassen sich Query-Pläne mit virtuellen
Indizes testen, ohne dass die Indizes physisch angelegt werden müssen. Nützlich für Index-Optimierung und Kapazitätsplanung.
**Use Case:** Testen verschiedener Index-Strategien auf Produktions-Replikat ohne Schreiblast und Speicherverbrauch.
#### pg_repack für Index-Maintenance
Extension "pg_repack" für Online-Reorganisation von Tabellen und Indizes (Beseitigung von
Bloat) mit minimalen Locks. Eignet sich für Wartungsarbeiten in OLTP-Systemen.
**Use Case:** Nächtliche Bloat-Beseitigung in stark fragmentierten Indizes ohne Downtime in 24/7-System.
## Wichtige Hinweise zur Nutzung
### Clustered vs. Non-Clustered Indexes
PostgreSQL kennt im Gegensatz zu anderen Datenbanksystemen (z.B. SQL Server, MySQL InnoDB) keine integrierten clustered indexes, bei denen die Tabellendaten permanent in der Reihenfolge eines Index physisch gespeichert werden. Die einzige Ausnahme bildet der B-Tree-Index mit INCLUDE-Klausel (Covering Index), der zusätzliche Spalten im Index speichert.
PostgreSQL bietet jedoch das `CLUSTER`-Kommando, um Tabellendaten temporär nach einem Index zu sortieren:
```sql
CLUSTER angestellter USING angestellter_pkey;
```
Dies ordnet die Tabelle `angestellter` physisch nach dem Index `angestellter_pkey` und kann die Performance von Range-Scans erheblich verbessern. **Wichtig:** Diese physische Sortierung ist nicht persistent - bei nachfolgenden INSERTs, UPDATEs oder DELETEs wird die Clustering-Ordnung nicht aufrechterhalten. Das CLUSTER-Kommando muss periodisch wiederholt werden (z.B. in nächtlichen Wartungsfenstern), um die physische Sortierung beizubehalten. Während des CLUSTER-Vorgangs wird ein exklusiver Lock auf die Tabelle gesetzt.
### Concurrent Index Creation
Bei der Erstellung von Indizes in Produktionssystemen sollte die `CONCURRENTLY`-Option verwendet werden, um Schreibblockaden zu vermeiden:
```sql
CREATE INDEX CONCURRENTLY idx_name ON table (column);
```
Dies dauert zwar länger, vermeidet aber das Blockieren von INSERTs, UPDATEs und DELETEs während der Index-Erstellung.
### Index-Nutzung überprüfen
Die System-View `pg_stat_user_indexes` zeigt, wie oft jeder Index tatsächlich verwendet wird. Ungenutzte Indizes sollten entfernt werden, da sie Speicher verschwenden und Schreiboperationen verlangsamen.
## Danksagung, Kontakt, Lizenz
Teile dieses Dokuments sind der Seminararbeit "Übersicht über
Datenbank-Index-Typen in bekannten Datenbanksystemen mit
PostgreSQL-Benchmark" von Vladimir Brazhnik (2021) entnommen: Kudos.
Kontakt: Institut für Software, Departement Informatik, OST Campus
Rapperswil, E-Mail.
Lizenz CC BY 3.0 CH.