Ein Index, mehrere Spalten
Ein Composite Index – auch bekannt als Multi-Column-Index oder schlicht Index über mehrere Spalten – ist ein einzelner Index, der über zwei oder mehr Spalten aufgebaut wird. Du erstellst ihn, indem du die Spalten in der gewünschten Reihenfolge angibst:
Der Index idx_orders_customer_status legt seine Einträge sortiert ab – zuerst nach customer_id, und innerhalb jedes Kunden dann nach status. Genau diese Sortierreihenfolge ist der Schlüssel zu allem: Jedes andere Verhalten eines Composite Index in SQLite ergibt sich direkt daraus.
Das mentale Modell: ein sortiertes Telefonbuch
Stell dir ein altes Telefonbuch vor. Die Einträge sind nach Nachname sortiert, und innerhalb desselben Nachnamens nach Vorname. Genau so sieht ein Index auf (last_name, first_name) aus.
Manche Abfragen sind dabei billig, andere nicht:
- „Finde alle mit Nachname Patel" – einfach, alle Patels stehen direkt beieinander.
- „Finde Priya Patel" – einfach, zu Patel springen und dann bis Priya weiterlesen.
- „Finde alle mit Vorname Priya" – langsam, du musst jede Seite durchblättern. Priyas verteilen sich quer über alle Nachnamen.
Ein SQLite Composite Index funktioniert nach demselben Prinzip. Die erste Spalte ist der primäre Sortierschlüssel; die zweite Spalte sortiert nur noch Einträge, die in der ersten Spalte denselben Wert haben.
Die Leftmost-Prefix-Regel in SQLite
SQLite kann einen Composite Index nur dann für eine Abfrage nutzen, wenn die WHERE-Klausel ein linkes Präfix seiner Spalten einschränkt. Für einen Index auf (a, b, c) heißt das:
- Filter auf
a– Index wird genutzt. - Filter auf
aundb– Index wird genutzt. - Filter auf
a,bundc– Index wird genutzt. - Filter nur auf
b, nur aufcoder aufbundc– Index wird nicht genutzt.
Das lässt sich direkt mit EXPLAIN QUERY PLAN überprüfen:
Der erste Plan zeigt SEARCH events USING INDEX idx_events_user_kind_time. Der zweite fällt auf SCAN events zurück – wer nur nach kind filtert, überspringt die führende Spalte user_id, und damit ist der Index für diese Query wertlos.
Spaltenreihenfolge im SQLite Composite Index ist eine Designentscheidung
Weil die Leftmost-Prefix-Regel greift, ist die Reihenfolge der Spalten in CREATE INDEX eine echte Entscheidung – keine Frage des Geschmacks. Zwei Faustregeln helfen dir dabei:
- Die Spalte, nach der du am häufigsten filterst, gehört nach vorn. Sie öffnet den Index für die größte Bandbreite an Queries.
- Gleichheitsspalten vor Bereichsspalten. SQLite kann sich per
=in den Index reinhangeln und danach mit<,>oderBETWEENeinen zusammenhängenden Bereich durchscannen – aber nur auf der letzten genutzten Spalte.
Der Plan zeigt SEARCH sales USING INDEX idx_sales_region_time (region=? AND sold_at>?). SQLite springt direkt zu region = 'EU' und läuft dann durch den Datumsbereich nach vorne. Drehst du die Spaltenreihenfolge zu (sold_at, region) um, muss dieselbe Abfrage jede Zeile im Datumsbereich scannen und für jede einzelne region erneut prüfen.
Composite Index vs. mehrere Single-Column-Indexe
Eine Frage, die immer wieder auftaucht: Soll ich lieber einen Index auf (a, b) anlegen oder zwei separate Indexe auf a und b?
Beim kombinierten Filter ist der Composite Index schneller — SQLite springt direkt zu den passenden (project_id, state)-Einträgen. Mit zwei einzelnen Indizes auf jeweils einer Spalte wählt SQLite normalerweise einen davon aus, grenzt damit die Zeilen ein und prüft dann bei jeder Trefferzeile die andere Spalte nach. Manchmal werden beide Indizes auch miteinander verschnitten, aber wenn die Spalten zusammen abgefragt werden, ist der Composite Index die saubere Lösung.
Werden project_id und state zusätzlich auch einzeln abgefragt, kann es sinnvoll sein, beides anzulegen — den Composite Index für die kombinierte Abfrage und einen einspaltigen Index auf state für Queries, die nur danach filtern.
Covering Index in SQLite
Enthält ein Index alle Spalten, die eine Query braucht — sowohl die Filterspalten als auch die ausgewählten Spalten — kann SQLite die Abfrage komplett aus dem Index beantworten, ohne die Tabelle überhaupt anzufassen. Das nennt man einen Covering Index, und schneller geht's nicht.
Der Ausführungsplan zeigt USING COVERING INDEX idx_invoices_cover. Die Query liest issued_at und total direkt aus dem Index — notes und id werden gar nicht gebraucht, also wird die Tabelle selbst überhaupt nicht angefasst. Eine Spalte ausschließlich deshalb in einen Composite Index aufzunehmen, um eine viel genutzte Query als Covering Index abzudecken, ist ein lohnender Deal, wenn diese Query ständig läuft.
Zusammengesetzte UNIQUE-Constraints
Ein SQLite Composite Index kann auch Eindeutigkeit über mehrere Spalten hinweg erzwingen. Praktisch immer dann, wenn keine einzelne Spalte für sich genommen eindeutig ist, die Kombination aber sehr wohl eindeutig sein muss:
Beim dritten Insert schlägt SQLite mit UNIQUE constraint failed: enrollments.student_id, enrollments.course_id fehl. Das Paar existiert bereits im Index, deshalb lehnt SQLite das Duplikat ab.
Stolperfallen, die du kennen solltest
ORzwischen Nicht-Führungsspalten blockiert den Index. Bei einem Index(a, b)kannWHERE a = 1 OR b = 2den Index in der Regel gar nicht nutzen — SQLite muss beide Zweige getrennt betrachten.- Funktionen auf indizierten Spalten machen den Index unbrauchbar.
WHERE lower(email) = 'x'nutzt keinen Index aufemail. Indexiere stattdessen den Ausdruck selbst oder normalisiere die Daten schon beim Einfügen. - Indizes sind nicht umsonst. Jeder Index wird bei jedem
INSERT, jedemUPDATE(auf indizierten Spalten) und jedemDELETEmit aktualisiert. Drei Composite Indexes auf einer schreibintensiven Tabelle können die Schreibkosten dominieren. - Nach dem Anlegen von Indizes
ANALYZEausführen. Der Planner von SQLite nutzt die Statistiken, dieANALYZEsammelt, um zwischen Indexkandidaten zu wählen. Ohne diese Statistiken greift er auf Heuristiken zurück, die nicht immer optimal sind.
Ein praxistauglicher Workflow
Beim Tuning einer langsamen Abfrage sieht die Schleife meist so aus:
EXPLAIN QUERY PLANauf die Abfrage anwenden, um zu sehen, was SQLite aktuell tut.- Wenn gescannt wird: schau dir die
WHERE-Klausel an — welche Spalte ist die Gleichheitsspalte? Welche der Range-Filter? Was wird ausgewählt? - Einen Composite Index bauen, in der Reihenfolge erst Gleichheit, dann Range, und bei Bedarf die ausgewählten Spalten anhängen, falls ein Covering Index hilft.
ANALYZEausführen.EXPLAIN QUERY PLANerneut laufen lassen. Bestätige, dass sich der Plan geändert hat und der Index tatsächlich genutzt wird.- Die Abfrage vor und nach der Änderung mit realistischen Daten messen.
Schritt 6 zu überspringen ist riskant. Ein Index, der im Plan korrekt aussieht, kann in der Praxis trotzdem langsamer sein — etwa wenn die Tabelle klein ist oder der Planner einen anderen Weg wählt.
Als Nächstes: Partial Indexes
Composite Indexes decken jede Zeile der Tabelle ab. Oft interessiert dich aber nur ein kleiner Teil der Zeilen — offene Tickets, unbearbeitete Jobs, nicht gelöschte Datensätze. Mit einem Partial Index kannst du genau diese Zeilen indizieren, indem du eine WHERE-Klausel direkt in den Index einbaust. Darum geht es auf der nächsten Seite.
Häufig gestellte Fragen
Was ist ein Composite Index in SQLite überhaupt?
Ein Composite Index ist ein einzelner Index, der zwei oder mehr Spalten umfasst. Anlegen lässt er sich mit CREATE INDEX idx_name ON table(col_a, col_b). SQLite sortiert die Einträge zuerst nach col_a, dann innerhalb jedes col_a-Werts nach col_b – ähnlich wie ein Telefonbuch, das erst nach Nachname und dann nach Vorname sortiert ist.
Spielt die Spaltenreihenfolge beim Composite Index eine Rolle?
Und wie. SQLite kann einen zusammengesetzten Index nur dann nutzen, wenn die WHERE-Klausel auf einem führenden Präfix der indizierten Spalten filtert (Leftmost-Prefix-Regel). Ein Index auf (a, b, c) greift bei Queries, die nach a, nach a und b oder nach allen drei filtern – aber nicht, wenn nur nach b oder nur nach c gesucht wird.
Wann ist ein Composite Index sinnvoller als mehrere Single-Column-Indizes?
Immer dann, wenn deine Queries regelmäßig nach derselben Spaltenkombination filtern oder sortieren. Werden die Spalten dagegen unabhängig voneinander abgefragt, sind einzelne Indizes pro Spalte die bessere Wahl. Mit EXPLAIN QUERY PLAN siehst du, welchen Index SQLite tatsächlich wählt – das ist das einzige verlässliche Feedback.
Was ist ein Covering Index in SQLite?
Ein Covering Index enthält alle Spalten, die eine Query benötigt. SQLite kann die Anfrage dann komplett aus dem Index beantworten, ohne die eigentliche Tabelle anzufassen. EXPLAIN QUERY PLAN zeigt in dem Fall USING COVERING INDEX an. Einem Composite Index gezielt zusätzliche Spalten zu spendieren, nur damit eine heiße Query covered wird, ist ein gängiger Performance-Trick.