An index is a sorted side-structure that lets the database find rows without reading the whole table. The tool shows the payoff — flip the index on and rows examined drop from all of them to one or two. Here is the machinery behind that, plus the kinds of index worth knowing.
Where you meet it
- Columns in a
WHEREclause — looking up a user by email, a row by id. JOINconditions andORDER BY— both lean on the same sorted structure.- A primary key: most databases create an index for it automatically.
- The "why is this query suddenly slow on 10M rows?" moment.
What it does
It trades a little write cost and disk space for a lot of read speed. Instead of a full table scan — checking every row, O(n) — the engine walks a sorted structure to the answer in O(log n). On a million rows that is roughly the difference between 1,000,000 checks and 20.
An index buys you O(log n) reads by taxing every write — so index what you filter on, not everything you have.
How it works
The classic index is a B-tree: a balanced search tree whose leaves hold the indexed values in order, each pointing back to its row. Same idea as the index at the back of a book — sorted entries with page numbers, so you jump instead of reading cover to cover. Because the tree is sorted, the engine does a few comparisons to narrow down, then follows the pointer to the row.
CREATE INDEX idx_users_email ON users(email);
-- now this is a binary search, not a scan:
SELECT * FROM users WHERE email = 'gia@gp.io';
A composite index covers several columns, and order matters — it is sorted by the first column, then the second within that, like a phone book sorted by surname, then first name. So (last_name, first_name) helps a search on last_name alone, but not on first_name alone. A covering index goes one step further: it carries every column the query needs, so the engine answers straight from the index and never touches the table at all.
-- composite: leftmost column leads
CREATE INDEX idx_name ON users(last_name, first_name);
-- covering: the query is answered from the index itself
CREATE INDEX idx_cover ON users(last_name) INCLUDE (email);
Watch out
- Every index slows writes. Each
INSERT,UPDATEandDELETEhas to keep every index in sync — reads get faster, writes get heavier. - A function on the column disables the index.
WHERE LOWER(email) = …can't use a plain index onemail— index the expression instead, or store it already-normalised. - Don't index everything. Unused indexes cost space and write time for nothing. Index the columns you actually filter, join and sort on.
Go deeper
- PostgreSQL docs — Indexes (introduction & maintenance cost)
- PostgreSQL docs — Index-Only Scans & Covering Indexes
- SQLite — Query Planning (O(log n) lookups, multi-column indexes)
- Use The Index, Luke! — Anatomy of an Index (Markus Winand)
- Use The Index, Luke! — Concatenated (composite) keys: column order
Ein Index ist eine sortierte Hilfsstruktur, mit der die Datenbank Zeilen findet, ohne die ganze Tabelle zu lesen. Das Tool zeigt den Effekt — Index an, und die geprüften Zeilen fallen von allen auf ein, zwei. Hier kommt die Mechanik dahinter — plus die Index-Arten, die man kennen sollte.
Wo es vorkommt
- Spalten in einer
WHERE-Klausel — eine:n User:in per E-Mail suchen, eine Zeile per id. JOIN-Bedingungen undORDER BY— beide stützen sich auf dieselbe sortierte Struktur.- Ein Primärschlüssel: die meisten Datenbanken legen dafür automatisch einen Index an.
- Der "warum ist die Query bei 10 Mio. Zeilen plötzlich lahm?"-Moment.
Was es tut
Es tauscht etwas Schreib-Kosten und Speicher gegen viel Lese-Tempo. Statt eines Full Table Scans — jede Zeile prüfen, O(n) — läuft die Engine durch eine sortierte Struktur zur Antwort, in O(log n). Bei einer Million Zeilen ist das grob der Unterschied zwischen 1.000.000 Prüfungen und 20.
Ein Index erkauft dir O(log n)-Lesen, indem er jedes Schreiben besteuert — indiziere also, wonach du filterst, nicht alles, was du hast.
Wie es funktioniert
Der klassische Index ist ein B-Tree: ein balancierter Suchbaum, dessen Blätter die indizierten Werte sortiert halten, jeder mit Zeiger auf seine Zeile. Dieselbe Idee wie das Stichwortverzeichnis hinten im Buch — sortierte Einträge mit Seitenzahlen, also springst du, statt vorn bis hinten zu lesen. Weil der Baum sortiert ist, grenzt die Engine mit wenigen Vergleichen ein und folgt dann dem Zeiger zur Zeile.
CREATE INDEX idx_users_email ON users(email);
-- jetzt ist das eine binäre Suche, kein Scan:
SELECT * FROM users WHERE email = 'gia@gp.io';
Ein Composite Index deckt mehrere Spalten ab, und die Reihenfolge zählt — sortiert wird nach der ersten Spalte, dann innerhalb davon nach der zweiten, wie ein Telefonbuch nach Nachname, dann Vorname. (last_name, first_name) hilft also bei einer Suche nur auf last_name, aber nicht nur auf first_name. Ein Covering Index geht noch einen Schritt weiter: er trägt jede Spalte, die die Query braucht, sodass die Engine direkt aus dem Index antwortet und die Tabelle gar nicht mehr anfasst.
-- composite: die linke Spalte führt
CREATE INDEX idx_name ON users(last_name, first_name);
-- covering: die Query wird aus dem Index selbst beantwortet
CREATE INDEX idx_cover ON users(last_name) INCLUDE (email);
Worauf achten
- Jeder Index verlangsamt das Schreiben. Jedes
INSERT,UPDATEundDELETEmuss jeden Index mitpflegen — Lesen wird schneller, Schreiben schwerer. - Eine Funktion auf der Spalte schaltet den Index aus.
WHERE LOWER(email) = …kann einen normalen Index aufemailnicht nutzen — stattdessen den Ausdruck indizieren oder den Wert schon normalisiert speichern. - Nicht alles indizieren. Ungenutzte Indizes kosten Speicher und Schreibzeit für nichts. Indiziere die Spalten, nach denen du wirklich filterst, joinst und sortierst.
Mehr dazu
- PostgreSQL-Doku — Indexes (Einführung & Pflege-Kosten)
- PostgreSQL-Doku — Index-Only Scans & Covering Indexes
- SQLite — Query Planning (O(log n)-Lookups, Multi-Column-Indizes)
- Use The Index, Luke! — Anatomy of an Index (Markus Winand)
- Use The Index, Luke! — Concatenated (Composite) Keys: Spaltenreihenfolge