Covering-Indizes framework-unabhängig verstehen
AI generated
SELECT
JOIN
SQL / Indexierung
Covering-Indizes framework-unabhängig verstehen
wie ein Index den Table-Lookup komplett überflüssig macht

Ein normaler Index beschleunigt das Finden von Zeilen anhand einer Filterbedingung, verlangt danach aber in den meisten Fällen einen zusätzlichen Zugriff auf die eigentliche Tabelle, um die restlichen angefragten Spalten zu holen. Dieser zusätzliche Sprung, oft Key Lookup oder Bookmark Lookup genannt, kostet bei vielen Treffern spürbar Zeit. Ein Covering-Index enthält alle für eine Abfrage benötigten Spalten bereits selbst und macht den Lookup dadurch vollständig überflüssig, ein Effekt, der als Index-Only-Scan bezeichnet wird.

10 Min. Lesezeit Covering Index Index-Only-Scan

1. Wie ein normaler Index arbeitet und warum ein Lookup nötig wird

Ein klassischer sekundärer Index speichert die indizierten Spaltenwerte zusammen mit einem Verweis auf die vollständige Zeile, entweder direkt als physische Zeilenadresse oder, bei einem geclusterten Primärschlüssel, als Wert dieses Primärschlüssels. Findet die Datenbank über den Index passende Einträge, enthält der Index selbst zunächst nur die indizierten Spalten und den Verweis, nicht aber die übrigen von der Abfrage angefragten Spalten.

Werden weitere Spalten benötigt, die nicht Teil des Index sind, muss die Datenbank für jeden gefundenen Eintrag zusätzlich die vollständige Zeile aus der Tabelle nachladen. Dieser zweite Zugriff heißt Key Lookup oder Bookmark Lookup und bedeutet bei einem Index, der auf einer anderen physischen Struktur liegt als die Tabelle selbst, einen weiteren, oft zufälligen I/O-Zugriff pro Treffer.

2. Warum sich Lookup-Kosten bei vielen Treffern deutlich summieren

Bei einer Abfrage mit wenigen Treffern fällt ein zusätzlicher Lookup pro Zeile kaum ins Gewicht, weil die absolute Anzahl zusätzlicher Zugriffe klein bleibt. Bei einer Abfrage, die Tausende oder Millionen Zeilen über den Index findet und für jede davon einen separaten Lookup ausführt, summieren sich diese vielen kleinen, oft zufällig über die Platte verteilten Zugriffe zu einer erheblichen Gesamtlast, die den theoretischen Vorteil des Index teilweise wieder aufzehrt.

Genau an diesem Punkt setzt der Optimizer eine Kostenschätzung an: Ab einer gewissen geschätzten Trefferzahl kann ein voller Tabellen-Scan trotz vorhandenem Index günstiger erscheinen als ein Index-Scan mit vielen Einzel-Lookups, weil ein sequentieller Scan I/O-technisch effizienter ist als viele verstreute Einzelzugriffe. Dieses Verhalten wird oft fälschlich als Beweis dafür gedeutet, dass der Index defekt oder ungeeignet ist, obwohl es eine rationale Kostenentscheidung ist.

3. Das Grundprinzip eines Covering-Index

Ein Covering-Index löst dieses Problem, indem er neben den eigentlichen Filter- und Sortierspalten zusätzlich alle Spalten enthält, die eine bestimmte Abfrage sonst noch benötigt. Findet die Datenbank passende Einträge im Index, stehen alle angefragten Werte bereits im Index selbst zur Verfügung, ein zusätzlicher Zugriff auf die Tabelle entfällt vollständig. Dieses Muster wird als Index-Only-Scan bezeichnet und ist meist deutlich schneller als ein Index-Scan mit anschließenden Einzel-Lookups.

Wichtig dabei ist die Unterscheidung zwischen Schlüsselspalten, die die Sortierreihenfolge des Index bestimmen und für Bereichsfilter oder Sortierung genutzt werden, und zusätzlichen, nicht-schlüsselartigen Spalten, die lediglich mitgeführt werden, um Lookups zu vermeiden, ohne selbst die Sortierreihenfolge zu beeinflussen. Viele Datenbanksysteme bieten dafür eine explizite INCLUDE-Klausel an.


-- Abfrage, die einen Lookup erfordert bei nur teilweise passendem Index
CREATE INDEX idx_orders_customer ON orders (customer_id);

SELECT order_id, order_date, total_amount
FROM orders
WHERE customer_id = 4711;
-- Index findet die Zeile, order_date/total_amount müssen
-- per Lookup aus der Tabelle nachgeladen werden

-- Covering-Index: enthält alle benötigten Spalten
CREATE INDEX idx_orders_customer_covering
ON orders (customer_id) INCLUDE (order_date, total_amount);
-- Dieselbe Abfrage wird jetzt als Index-Only-Scan ausgeführt

4. Praktisches Beispiel: Index-Only-Scan im Ausführungsplan erkennen

Im Ausführungsplan zeigt sich der Unterschied deutlich: Ohne Covering-Index taucht neben dem Index-Zugriff ein separater Schritt für den Tabellenzugriff auf, oft explizit als Key Lookup oder Bookmark Lookup bezeichnet, verbunden über eine Nested-Loop-Verknüpfung mit dem Index-Zugriff. Mit einem passenden Covering-Index entfällt dieser zweite Schritt vollständig, und der Plan zeigt nur noch einen einzigen Zugriff, häufig explizit als Index-Only-Scan oder Covering-Index-Scan ausgewiesen.

Bei der Analyse eines bestehenden, langsamen Ausführungsplans lohnt sich deshalb ein gezielter Blick darauf, ob nach einem Index-Seek oder Index-Scan noch ein zusätzlicher Lookup-Schritt folgt. Ist das der Fall und macht dieser Lookup einen erheblichen Anteil der Gesamtkosten aus, ist die Erweiterung des Index um die fehlenden Spalten meist die naheliegendste und wirksamste Optimierung.


EXPLAIN
SELECT order_id, order_date, total_amount
FROM orders
WHERE customer_id = 4711;

-- Ohne Covering-Index (Auszug):
--   -> Index Lookup on orders using idx_orders_customer
--   -> Key Lookup on orders (fetches order_date, total_amount)

-- Mit Covering-Index (Auszug):
--   -> Covering Index Scan on idx_orders_customer_covering
--      (kein zusätzlicher Table-Access-Schritt mehr)

5. Der Trade-off: größere Indizes gegen weniger Lookups

Jede zusätzliche Spalte in einem Covering-Index vergrößert diesen Index physisch, was mehr Speicherplatz auf der Platte und mehr Speicher im Buffer Pool oder Page Cache bindet. Ein größerer Index bedeutet außerdem mehr Daten, die bei jedem Insert, Update oder Delete auf die indizierten oder mitgeführten Spalten zusätzlich geschrieben und im Index-Baum sortiert eingefügt werden müssen, was Schreiblast erhöht.

Dieser Trade-off ist kein pauschales Argument gegen Covering-Indizes, sondern ein Abwägungsproblem, das pro Abfragemuster einzeln bewertet werden sollte: Bei einer sehr häufig ausgeführten, leseintensiven Abfrage mit vielen Treffern rechtfertigt die Lookup-Ersparnis meist den zusätzlichen Speicher- und Schreibaufwand deutlich. Bei einer seltenen Abfrage mit wenigen Treffern lohnt sich der zusätzliche Speicherplatz dagegen oft nicht.

6. INCLUDE-Spalten vs. zusätzliche Schlüsselspalten

Eine mitgeführte Spalte über INCLUDE unterscheidet sich technisch von einer zusätzlichen Schlüsselspalte, weil sie nicht Teil der sortierten Baumstruktur des Index ist, sondern nur an den Blattknoten mitgespeichert wird. Das reduziert den Overhead gegenüber einer vollwertigen zusätzlichen Schlüsselspalte, weil weder die Baumhöhe noch die Sortierlogik durch die zusätzliche Spalte beeinflusst werden, gleichzeitig kann eine INCLUDE-Spalte aber nicht für Bereichsfilter oder Sortierung genutzt werden.

Systeme, die keine explizite INCLUDE-Klausel kennen, erreichen denselben Effekt, indem die benötigten Spalten einfach als zusätzliche, nachrangige Schlüsselspalten in den Index aufgenommen werden. Der Effekt für den Index-Only-Scan ist funktional ähnlich, der Overhead beim Schreiben fällt aber tendenziell etwas höher aus, weil diese Spalten dann auch die Sortierreihenfolge und damit die Struktur des Baums mitbestimmen.

7. Warum SELECT * einen Covering-Index fast immer verhindert

Ein Covering-Index kann nur greifen, wenn die Menge der angefragten Spalten tatsächlich bekannt und begrenzt ist. Eine Abfrage mit SELECT * fordert implizit jede Spalte der Tabelle an, wodurch ein Covering-Index alle Spalten der Tabelle enthalten müsste, um wirksam zu sein, was den ursprünglichen Vorteil gegenüber der Tabelle selbst zunichtemacht. In der Praxis bedeutet SELECT * deshalb fast immer, dass der Optimizer auf einen klassischen Lookup oder gleich auf einen vollständigen Tabellen-Scan ausweicht.

Explizites Auflisten der tatsächlich benötigten Spalten ist deshalb nicht nur allgemein guter Stil, sondern eine direkte Voraussetzung dafür, dass ein Covering-Index überhaupt wirksam werden kann. Diese Disziplin bei der Spaltenauswahl ist gerade bei häufig ausgeführten, performance-kritischen Abfragen ein einfacher, aber oft unterschätzter Hebel.

8. Pflegeaufwand: Covering-Indizes an sich ändernde Abfragen anpassen

Ein Covering-Index ist eng an ein konkretes Abfragemuster gebunden. Ändert sich die Anwendung und fragt eine bislang häufig genutzte Abfrage plötzlich eine zusätzliche Spalte ab, die nicht im Index enthalten ist, fällt der Index-Only-Scan wieder auf einen Lookup zurück, ohne dass dies sofort auffällt, wenn nicht regelmäßig Ausführungspläne überprüft werden. Covering-Indizes brauchen deshalb mehr laufende Pflege als generische Indizes auf einzelnen Filterspalten.

In der Praxis bewährt sich, Covering-Indizes gezielt für eine kleine Zahl besonders häufiger, performance-kritischer Abfragen anzulegen und regelmäßig, etwa nach größeren Anwendungsänderungen, zu überprüfen, ob sie noch greifen. Für seltener genutzte oder sich ständig ändernde Abfragen lohnt sich der Pflegeaufwand eines maßgeschneiderten Covering-Index dagegen selten.

9. Eine praktische Entscheidungsregel für den Einsatz

Ein Covering-Index lohnt sich am ehesten für Abfragen, die häufig ausgeführt werden, eine überschaubare, stabile Menge an Spalten benötigen, und im Ausführungsplan aktuell einen spürbaren Lookup-Anteil an den Gesamtkosten zeigen. Fehlt einer dieser drei Faktoren, insbesondere eine hohe Ausführungshäufigkeit, ist der zusätzliche Speicher- und Pflegeaufwand meist nicht gerechtfertigt.

Vor jeder Entscheidung lohnt sich ein Blick in den aktuellen Ausführungsplan der betroffenen Abfrage mit realen Produktionsdaten, weil erst dort sichtbar wird, wie groß der Lookup-Anteil tatsächlich ist. Eine Optimierung ohne diesen Beleg bleibt spekulativ und kann im schlimmsten Fall Schreiblast erhöhen, ohne den Lesepfad messbar zu beschleunigen.

Aspekt Normaler Index Covering Index Konsequenz
Enthaltene Spalten Nur Filter-/Sortierspalten Zusätzlich alle angefragten Spalten Kein Lookup nötig bei Covering
Zusätzlicher Table-Access Key Lookup pro Treffer Entfällt vollständig Index-Only-Scan möglich
Indexgröße Kleiner Größer durch INCLUDE-Spalten Mehr Speicher- und Cache-Bedarf
Schreiblast Geringer Etwas höher Bei jedem Insert/Update mehr Daten
SELECT * Funktioniert, aber mit Lookup Verhindert Covering fast immer Explizite Spaltenliste nötig
Pflegeaufwand Gering, generisch nutzbar Eng an Abfragemuster gebunden Regelmäßige Plan-Prüfung nötig

Mironsoft

Datenbank-Optimierung, Query-Tuning und Migrationen

SQL-Abfragen, die bei Wachstum immer langsamer werden?

Wir analysieren und optimieren SQL-Datenbanken unabhängig vom eingesetzten System, planen sichere Migrationen und Schema-Änderungen und bringen Teams Query-Optimierung praxisnah bei.

Query-Optimierung

Langsame Abfragen analysieren und mit Indizes und Explain-Plänen gezielt beschleunigen.

Migrations-Planung

Schema-Änderungen und Datenmigrationen sicher und ohne Downtime umsetzen.

Team-Schulung

SQL-Grundlagen und Performance-Denken praxisnah im Entwicklerteam verankern.

10. Zusammenfassung

Covering-Indizes: Das Wichtigste auf einen Blick

Grundidee

Ein Covering-Index enthält alle für eine Abfrage benötigten Spalten und macht einen zusätzlichen Table-Lookup überflüssig.

Effekt

Der Ausführungsplan zeigt einen Index-Only-Scan statt Index-Zugriff plus separatem Key Lookup pro Treffer.

Trade-off

Größere, teurere Indizes beim Schreiben stehen weniger Lookups und schnelleren Leseabfragen gegenüber.

Voraussetzung

Nur eine explizite, begrenzte Spaltenliste statt SELECT * kann von einem Covering-Index tatsächlich profitieren.

11. FAQ: Covering-Indizes: Das Wichtigste auf einen Blick

1Was ist der Unterschied zwischen einem normalen Index und einem Covering-Index?
Ein normaler Index enthält nur die Filter- und Sortierspalten sowie einen Verweis auf die Zeile, ein Covering-Index enthält zusätzlich alle weiteren von der Abfrage angefragten Spalten, sodass kein zusätzlicher Zugriff auf die Tabelle nötig ist.
2Was ist ein Key Lookup oder Bookmark Lookup?
Der zusätzliche Zugriff auf die vollständige Tabellenzeile, den die Datenbank ausführt, wenn ein Index nicht alle angefragten Spalten enthält. Bei vielen Treffern summiert sich dieser Zugriff zu erheblichem Overhead.
3Was bedeutet Index-Only-Scan?
Ein Ausführungsschritt, bei dem alle benötigten Daten direkt aus dem Index gelesen werden, ohne die eigentliche Tabelle überhaupt anzufassen. Er ist nur mit einem passenden Covering-Index möglich.
4Warum verhindert SELECT * fast immer einen Covering-Index?
Weil SELECT * implizit jede Spalte der Tabelle anfordert. Ein Index müsste dann alle Spalten enthalten, um wirksam zu covern, was den Größenvorteil gegenüber der Tabelle selbst aufhebt.
5Was ist der Unterschied zwischen einer INCLUDE-Spalte und einer zusätzlichen Schlüsselspalte?
Eine INCLUDE-Spalte wird nur an den Blattknoten des Index gespeichert und beeinflusst die Sortierreihenfolge nicht, eine zusätzliche Schlüsselspalte ist Teil der sortierten Baumstruktur und kann für Bereichsfilter genutzt werden.
6Lohnt sich ein Covering-Index für jede Abfrage?
Nein. Er lohnt sich vor allem bei häufig ausgeführten Abfragen mit stabiler Spaltenmenge und spürbarem Lookup-Anteil im Ausführungsplan. Bei seltenen Abfragen überwiegt meist der zusätzliche Speicher- und Pflegeaufwand.
7Wie erkenne ich im Ausführungsplan, ob ein Lookup stattfindet?
Ein zusätzlicher Schritt nach dem Index-Zugriff, oft als Key Lookup oder Bookmark Lookup bezeichnet, zeigt einen fehlenden Covering-Index an. Fehlt dieser Schritt, greift bereits ein Index-Only-Scan.
8Erhöht ein Covering-Index die Schreiblast?
Ja, weil zusätzliche Spalten bei jedem Insert oder Update mitgeschrieben werden müssen. Bei INCLUDE-Spalten fällt dieser Overhead geringer aus als bei vollwertigen zusätzlichen Schlüsselspalten.
9Muss ein Covering-Index regelmäßig überprüft werden?
Ja, weil er eng an ein konkretes Abfragemuster gebunden ist. Ändert sich die Abfrage und fragt eine zusätzliche, nicht enthaltene Spalte ab, fällt der Index-Only-Scan wieder auf einen Lookup zurück.
10Ist ein Covering-Index datenbankübergreifend gleich umgesetzt?
Das Grundprinzip ist identisch, die konkrete Syntax unterscheidet sich aber. Manche Systeme bieten eine explizite INCLUDE-Klausel, andere erreichen denselben Effekt über zusätzliche, nachrangige Schlüsselspalten.