Menu

SQLite Index: CREATE INDEX richtig einsetzen

Wie Indexe in SQLite intern funktionieren, wann sie Abfragen beschleunigen, wann sie eher bremsen – und wie du prüfst, ob der Query Planner sie tatsächlich nutzt.

Diese Seite enthält ausführbare Editoren — bearbeiten, ausführen und Ausgabe sofort sehen.

Was ein Index wirklich ist

Ein Index ist eine eigenständige Datenstruktur — ein sortierter B-Baum — mit dem SQLite Zeilen anhand eines Spaltenwerts findet, ohne die gesamte Tabelle zu durchsuchen. Ohne Index liest eine Abfrage wie WHERE email = 'rosa@example.com' jede einzelne Zeile und prüft sie. Mit einem Index auf email läuft SQLite in etwa log(n) Schritten durch den Baum und springt direkt zum Treffer.

Dieser Geschwindigkeitsgewinn ist aber nicht umsonst. Der Index ist eine Kopie der indizierten Spalte plus ein Verweis auf die Zeile. Jedes INSERT, jedes UPDATE einer indizierten Spalte und jedes DELETE muss den Index ebenfalls aktualisieren. Der Speicherbedarf steigt, der Schreibdurchsatz sinkt ein wenig. Der Deal lautet: Du zahlst beim Schreiben, sparst aber beim Lesen ein Vielfaches davon.

SQLite Index erstellen

Die grundlegende Syntax:

Namenskonvention: In den meisten Teams hat sich idx_<tabelle>_<spalte> eingebürgert, damit auf den ersten Blick klar ist, wozu der Index dient. Der Name muss in der gesamten Datenbank eindeutig sein – nicht nur innerhalb der Tabelle. Genau deshalb steckt der Tabellenname mit drin.

So entfernst du einen Index wieder:

DROP INDEX idx_users_email;

Indexes sind reines Performance-Gerüst. Ein DROP INDEX rührt deine Daten nie an – es ändert nur, wie schnell deine Queries laufen.

Unique Index in SQLite

Ein Unique Index hat einen doppelten Zweck: Er beschleunigt Lookups und stellt gleichzeitig sicher, dass kein Wert in der indizierten Spalte doppelt vorkommt.

Der dritte Insert schlägt mit UNIQUE constraint failed: accounts.username fehl. SQLite legt für PRIMARY KEY- und UNIQUE-Spalten automatisch unique Indizes an – du erkennst sie am Namen sqlite_autoindex_<table>_<n>. Ein eigenes CREATE UNIQUE INDEX brauchst du also nur dann, wenn die Einschränkung nicht direkt an der Tabelle deklariert wurde.

Was der Query Planner tatsächlich macht

Nur weil ein Index existiert, heißt das noch lange nicht, dass SQLite ihn auch verwendet. Der Query Planner entscheidet pro Abfrage neu, welche Strategie er fährt – und mit EXPLAIN QUERY PLAN siehst du genau, wofür er sich entschieden hat:

Achte in der Ausgabe auf SEARCH ... USING INDEX idx_orders_customer – das bedeutet, der Index wird tatsächlich genutzt. Steht dort dagegen SCAN orders, hat der Planner einen Full Table Scan für günstiger gehalten (bei kleinen Tabellen oft die richtige Entscheidung) oder die Form deiner Query verhindert die Nutzung des Index. Wie man solche Pläne richtig liest, schauen wir uns gleich in einem eigenen Abschnitt an.

Wann ein SQLite-Index nicht genutzt wird

Indizes haben ein paar typische blinde Flecken. Jede der folgenden Situationen sorgt dafür, dass der Index auf email ignoriert wird:

-- Funktion umschließt die Spalte
SELECT * FROM users WHERE lower(email) = 'rosa@example.com';

-- Führendes Platzhalterzeichen in LIKE
SELECT * FROM users WHERE email LIKE '%@example.com';

-- Typkonflikt erzwingt eine Konvertierung
SELECT * FROM users WHERE email = 12345;

Der B-Baum ist nach dem rohen email-Wert sortiert. Sobald du die Spalte zur Query-Zeit transformierst, ist ein Full Scan unausweichlich. Mögliche Lösungen: Die Daten gleich normalisiert ablegen (etwa in einer Spalte email_lower), einen Expression Index anlegen (CREATE INDEX idx ON users(lower(email))) oder für Substring-Suchen die Volltextsuche von SQLite nutzen.

Covering Index in SQLite

Enthält ein Index bereits sämtliche Spalten, die deine Abfrage braucht, kann SQLite die Anfrage komplett aus dem Index beantworten – ohne die Tabelle überhaupt anzufassen. Genau das ist ein Covering Index. Der Kniff besteht darin, in der Index-Definition zusätzliche Spalten mit aufzunehmen:

Da beide Spalten, die das Query benötigt, bereits im Index liegen, meldet SQLite USING COVERING INDEX – ein Zugriff auf die eigentliche Zeile entfällt. Covering Indexes gehören zu den wirkungsvollsten Optimierungen für stark frequentierte Lesepfade. Der Preis dafür: ein größerer Index. Mehrspaltige Indexe sind ein eigenes Kapitel für sich – darum kümmert sich das nächste Dokument.

SQLite Indexe auflisten und inspizieren

Es gibt zwei Wege, sich anzusehen, was vorhanden ist:

Damit bekommst du jede Index-Definition in der Datenbank inklusive CREATE-Statement. Für eine einzelne Tabelle zeigt dir PRAGMA index_list('products'); nur die Indexe genau dieser Tabelle, und PRAGMA index_info('idx_products_name'); verrät dir, welche Spalten der jeweilige Index abdeckt. Alles, was mit sqlite_autoindex_ anfängt, wurde automatisch für einen PRIMARY KEY oder eine UNIQUE-Constraint angelegt — solche Indexe lassen sich nicht löschen.

Wann du keinen Index anlegen solltest

Es gibt durchaus Fälle, in denen ein zusätzlicher Index die Sache eher verschlechtert:

  • Sehr kleine Tabellen. Ein paar Hundert Zeilen sind in Mikrosekunden durchgescannt. Der Query Planner ignoriert den Index dann meist sowieso — du hast dir nur unnötigen Schreib-Overhead eingehandelt.
  • Schreiblastige Spalten, die kaum abgefragt werden. Jeder Schreibvorgang aktualisiert jeden Index. Eine Spalte zu indizieren, nach der du fast nie filterst, ist reiner Mehraufwand ohne Nutzen.
  • Spalten mit geringer Kardinalität, die alleine stehen. Ein Index auf einer status-Spalte mit drei möglichen Werten grenzt kaum etwas ein. Als zweite Spalte in einem zusammengesetzten Index oder als Partial Index kann das durchaus Sinn ergeben — alleinstehend lohnt es sich oft nicht.
  • Bereits abgedeckt. Wenn du einen Index auf (a, b) hast, brauchst du keinen zusätzlichen auf (a). SQLite nutzt die führenden Spalten eines zusammengesetzten Index auch dann, wenn du nur nach a filterst.

Die ehrliche Antwort auf die Frage „Soll ich diesen Index anlegen?" lautet fast immer: ausprobieren, mit EXPLAIN QUERY PLAN analysieren, mit realistischen Daten messen, dann entscheiden.

Als Nächstes: Zusammengesetzte Indexe

Ein Index auf einer einzelnen Spalte deckt schon viel ab, aber echte Queries filtern und sortieren oft über mehrere Spalten gleichzeitig. Genau dafür gibt es zusammengesetzte Indexe — also Indexe auf (a, b, c) — und die Spaltenreihenfolge ist dabei wichtiger, als die meisten denken. Darum geht es auf der nächsten Seite.

Häufig gestellte Fragen

Wie lege ich in SQLite einen Index an?

Mit CREATE INDEX index_name ON table_name(column_name);. Soll der Wert eindeutig sein, nimmst du CREATE UNIQUE INDEX. Wichtig: Der Indexname muss in der gesamten Datenbank eindeutig sein, nicht nur innerhalb der Tabelle. Entfernen kannst du ihn jederzeit mit DROP INDEX index_name;.

Wann lohnt sich ein Index in SQLite überhaupt?

Immer dann, wenn du eine Spalte häufig in WHERE, JOIN oder ORDER BY benutzt – vor allem bei großen Tabellen, in denen die Abfrage nur einen kleinen Teil der Zeilen zurückgibt. Indexe sind aber kein Gratis-Boost: Jeder zusätzliche Index verlangsamt INSERT, UPDATE und DELETE und kostet Plattenplatz. Prüf deshalb immer mit EXPLAIN QUERY PLAN, ob der Planner den Index tatsächlich verwendet.

Warum benutzt SQLite meinen Index nicht?

Typische Gründe: Die Tabelle ist so klein, dass ein Full Scan günstiger ist. Die Spalte steckt in einem Funktionsaufruf – WHERE lower(email) = ... greift nicht auf einen normalen Index auf email zu. Oder das Query enthält ein OR über nicht indexierte Spalten. Manchmal sind auch nur die Statistiken veraltet: Dann hilft ANALYZE, gefolgt von EXPLAIN QUERY PLAN, um zu sehen, wofür der Planner sich entschieden hat.

Wie liste ich alle Indexe einer Tabelle auf?

Für eine bestimmte Tabelle nimmst du PRAGMA index_list('table_name');. Alternativ fragst du sqlite_master direkt ab: SELECT name, sql FROM sqlite_master WHERE type = 'index';. Einträge mit dem Präfix sqlite_autoindex_* sind übrigens automatisch erzeugte Indexe, die SQLite für PRIMARY KEY- und UNIQUE-Constraints anlegt.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S