Warum tiefe Seiten mit OFFSET immer langsamer werden
OFFSET-basierte Pagination wird mit jeder weiteren Seite langsamer, weil die Datenbank alle uebersprungenen Zeilen trotzdem lesen und verwerfen muss. Keyset-Pagination, auch Cursor-Pagination genannt, ersetzt den Seitenzaehler durch einen Vergleich mit dem letzten gesehenen Wert und liefert dadurch konstante Antwortzeiten, unabhaengig davon, wie tief man in die Ergebnismenge blaettert.
Inhaltsverzeichnis
- 1. Warum die Wahl der Pagination-Strategie ueberhaupt relevant ist
- 2. Wie OFFSET-Pagination funktioniert und warum sie langsam wird
- 3. Keyset-Pagination im Detail: WHERE id > last_seen_id
- 4. Warum eine stabile Sortierreihenfolge zwingend noetig ist
- 5. Mehrspaltige Sortierung und zusammengesetzte Cursor
- 6. Das Problem mit Seitenzahlen und direktem Sprung zu Seite N
- 7. Konsistenz bei gleichzeitigen Schreibvorgaengen
- 8. Hybride Ansaetze und wann OFFSET noch vertretbar ist
- 9. OFFSET und Keyset im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum die Wahl der Pagination-Strategie ueberhaupt relevant ist
Fast jede Anwendung mit groesseren Ergebnismengen braucht Pagination, um Daten in handhabbaren Haeppchen statt in einer riesigen Antwort auszuliefern. Die naheliegendste Implementierung, LIMIT und OFFSET, funktioniert auf den ersten Seiten einwandfrei und wird deshalb selten hinterfragt. Genau diese scheinbare Einfachheit ist das Problem: Keyset-Pagination loest ein Skalierungsproblem, das bei OFFSET erst dann sichtbar wird, wenn Nutzer tief in Ergebnislisten blaettern oder ein Dataset waechst.
Der Unterschied zwischen beiden Ansaetzen ist kein akademisches Detail, sondern hat direkte Auswirkungen auf die User Experience. Eine API, die auf Seite 500 mehrere Sekunden fuer eine einfache Listenabfrage braucht, waehrend Seite 1 in Millisekunden antwortet, verschlechtert Ladezeiten und Nutzererfahrung genau dort, wo Nutzer oder automatisierte Crawler tief durch Daten iterieren, etwa bei Exportfunktionen, Infinite Scroll oder API-Konsumenten, die systematisch alle Datensaetze abrufen.
Dieser Artikel erklaert, warum OFFSET strukturell nicht skaliert, wie Keyset-Pagination als Alternative funktioniert, und in welchen Faellen OFFSET trotzdem eine vertretbare Wahl bleibt. Die Prinzipien sind unabhaengig vom konkreten Datenbanksystem, auch wenn die Syntax zwischen MySQL, PostgreSQL und SQL Server leicht variiert.
Besonders relevant wird das Thema bei Systemen, die von Anfang an fuer Wachstum ausgelegt sein sollen. Eine Pagination-Strategie, die bei tausend Zeilen unauffaellig funktioniert, kann bei zehn Millionen Zeilen zum dominanten Performance-Problem der gesamten Anwendung werden, oft ohne dass eine einzelne Codeaenderung dafuer verantwortlich war, sondern schlicht durch organisches Datenwachstum. Wer Keyset-Pagination von Beginn an einplant, vermeidet eine schmerzhafte Migration mitten im produktiven Betrieb.
2. Wie OFFSET-Pagination funktioniert und warum sie langsam wird
LIMIT und OFFSET sind die intuitivste Form der Pagination: LIMIT 20 OFFSET 100 liefert zwanzig Zeilen, nachdem die ersten hundert uebersprungen wurden. Das Problem liegt in der Ausfuehrung: Die Datenbank kann in der Regel nicht direkt zur hundertsten Zeile springen, sie muss die ersten hundertzwanzig Zeilen gemaess der Sortierreihenfolge tatsaechlich lesen und verarbeiten, die ersten hundert davon dann verwerfen und nur die letzten zwanzig zurueckgeben. Der Aufwand fuer OFFSET waechst also linear mit der Anzahl uebersprungener Zeilen, selbst wenn ein Index die Sortierung unterstuetzt.
Bei kleinen Offsets, etwa den ersten paar Seiten, ist dieser Effekt kaum spuerbar, weil die Anzahl der zu verwerfenden Zeilen gering ist. Bei Seite 500 mit 20 Zeilen pro Seite muss die Datenbank aber bereits 10.000 Zeilen lesen und verwerfen, bevor sie die eigentlich gewuenschten 20 Zeilen zurueckgibt. Diese Diskrepanz zwischen "Zeilen gelesen" und "Zeilen zurueckgegeben" ist der Kern des Problems, das Keyset-Pagination strukturell vermeidet.
Ein zusaetzlicher, oft uebersehener Effekt: Auch wenn ein Index die ORDER-BY-Spalte abdeckt und theoretisch einen Index-Scan statt eines Full Table Scans ermoeglicht, muss die Datenbank trotzdem sequenziell durch den Index navigieren, um die uebersprungenen Eintraege zu zaehlen. Der Index verhindert also nicht das grundlegende Problem von OFFSET, er macht die Verschwendung nur etwas weniger teuer pro Zeile, aendert aber nichts an der linearen Wachstumsrate mit der Seitentiefe.
-- Page 1: fast, few rows to skip
SELECT product_id, product_name, price
FROM products
ORDER BY product_id
LIMIT 20 OFFSET 0;
-- Page 500: the database still reads and discards 10,000 rows first
SELECT product_id, product_name, price
FROM products
ORDER BY product_id
LIMIT 20 OFFSET 10000;
-- Cost grows linearly with OFFSET, even with an index on product_id
3. Keyset-Pagination im Detail: WHERE id > last_seen_id
Keyset-Pagination, auch Cursor-Pagination genannt, ersetzt den numerischen Seitenversatz durch einen Wertvergleich mit dem letzten gesehenen Datensatz. Statt "ueberspringe die ersten hundert Zeilen" fragt man "gib mir die naechsten zwanzig Zeilen nach diesem konkreten Wert". Diese Formulierung erlaubt der Datenbank, direkt ueber den Index an die richtige Position zu springen, ohne vorherige Zeilen lesen und verwerfen zu muessen, weil ein B-Tree-Index bereits sortiert vorliegt und eine Bereichssuche ab einem bestimmten Wert in logarithmischer statt linearer Zeit moeglich macht.
Die grundlegende Syntax fuer Keyset-Pagination ist denkbar einfach: WHERE id > last_seen_id ORDER BY id LIMIT 20. Der Client speichert die ID der letzten Zeile der aktuellen Seite und sendet sie als Parameter fuer die naechste Anfrage. Die Antwortzeit dieser Abfrage bleibt konstant, egal ob man auf Seite 2 oder Seite 5000 ist, weil die Datenbank in beiden Faellen nur zwanzig Zeilen ab der gefundenen Position lesen muss, statt Tausende Zeilen vorher zu ueberspringen.
Der wichtigste konzeptionelle Unterschied zu OFFSET: Keyset-Pagination kennt keine absolute Seitenzahl mehr, sondern nur eine Position relativ zum zuletzt gesehenen Datensatz. Das ist fuer viele Anwendungsfaelle, etwa Infinite Scroll, API-Paginierung oder Export-Jobs, vollkommen ausreichend und sogar praktischer als absolute Seitenzahlen, waehrend es fuer klassische Seitennummerierung mit direktem Sprung zu Seite 47 zusaetzliche Ueberlegungen erfordert, die im Abschnitt zu Seitenzahlen behandelt werden.
-- First page: no cursor yet, just take the first 20 rows
SELECT product_id, product_name, price
FROM products
ORDER BY product_id
LIMIT 20;
-- Next page: use the last seen product_id from the previous page as the cursor
SELECT product_id, product_name, price
FROM products
WHERE product_id > 10240 -- last_seen_id from the previous page
ORDER BY product_id
LIMIT 20;
-- Constant-time lookup via the index, no rows skipped and discarded
4. Warum eine stabile Sortierreihenfolge zwingend noetig ist
Sowohl OFFSET als auch Keyset-Pagination benoetigen eine deterministische, stabile Sortierreihenfolge, aber bei Keyset-Pagination ist diese Anforderung noch strikter, weil der Cursor selbst auf der Sortierspalte basiert. Sortiert man beispielsweise nach einem Erstellungsdatum, das nicht eindeutig ist, weil mehrere Zeilen denselben Zeitstempel tragen koennen, ist die Reihenfolge dieser gleichwertigen Zeilen undefiniert, und der Cursor kann Zeilen ueberspringen oder doppelt zurueckgeben.
Die Loesung ist, immer nach einer eindeutigen Spalte oder Spaltenkombination zu sortieren, typischerweise dem Primary Key oder einer Kombination aus einer fachlichen Sortierspalte plus dem Primary Key als Tie-Breaker. ORDER BY created_at, id ist deutlich robuster als ORDER BY created_at allein, weil die ID als eindeutiger Tie-Breaker jede Mehrdeutigkeit zwischen Zeilen mit identischem Zeitstempel aufloest. Diese Anforderung an eine stabile Sortierung ist der wichtigste Grund, warum Keyset-Pagination sorgfaeltiger geplant werden muss als eine naive OFFSET-Loesung.
Ein weiterer wichtiger Punkt: Die Sortierspalte muss indiziert sein, sonst verpufft der gesamte Geschwindigkeitsvorteil von Keyset-Pagination, weil die Datenbank ohne Index trotzdem einen vollstaendigen Scan durchfuehren muesste, um die naechsten Zeilen nach dem Cursor-Wert zu finden. Ein zusammengesetzter Index ueber (created_at, id) unterstuetzt sowohl die Sortierung als auch den Vergleich im WHERE-Teil in einer einzigen Indexnutzung.
-- Unstable: created_at is not unique, ties can be skipped or duplicated
SELECT order_id, created_at FROM orders
WHERE created_at > '2026-07-01 10:15:00'
ORDER BY created_at
LIMIT 20;
-- Stable: id as a unique tie-breaker guarantees a deterministic order
SELECT order_id, created_at FROM orders
WHERE created_at > '2026-07-01 10:15:00'
ORDER BY created_at, order_id
LIMIT 20;
-- Supporting composite index for both sorting and the WHERE comparison
CREATE INDEX idx_orders_created_id ON orders (created_at, order_id);
5. Mehrspaltige Sortierung und zusammengesetzte Cursor
Sobald die Sortierung mehr als eine Spalte umfasst, wird der Cursor fuer Keyset-Pagination entsprechend komplexer. Statt eines einzelnen Vergleichs braucht man eine zusammengesetzte Bedingung, die die lexikografische Ordnung mehrerer Spalten korrekt abbildet. Fuer ORDER BY created_at, id lautet die korrekte WHERE-Bedingung nicht einfach zwei getrennte Vergleiche, sondern eine Tupel-Bedingung: entweder created_at ist groesser als der Cursor-Wert, oder created_at ist gleich und id ist groesser.
Viele Datenbanken, darunter PostgreSQL und MySQL ab Version 8, unterstuetzen direkt Tupel-Vergleiche in der Form WHERE (created_at, id) > (cursor_date, cursor_id), was die Bedingung deutlich lesbarer macht als die ausgeschriebene OR-Verknuepfung. Diese Syntax ist funktional aequivalent, aber weniger fehleranfaellig, weil man nicht manuell die Prioritaet der Spalten in einer verschachtelten OR-Bedingung nachbilden muss. Fuer Datenbanken ohne native Tupel-Vergleiche bleibt die ausgeschriebene Form die zuverlaessige Alternative fuer Keyset-Pagination mit mehreren Sortierspalten.
-- Multi-column keyset pagination: sort by created_at, then id as tie-breaker
-- Explicit form, works on virtually every SQL database
SELECT order_id, created_at, total_amount
FROM orders
WHERE (created_at > '2026-07-01 10:15:00')
OR (created_at = '2026-07-01 10:15:00' AND order_id > 88213)
ORDER BY created_at, order_id
LIMIT 20;
-- Row-value comparison, supported by PostgreSQL and MySQL 8+
SELECT order_id, created_at, total_amount
FROM orders
WHERE (created_at, order_id) > ('2026-07-01 10:15:00', 88213)
ORDER BY created_at, order_id
LIMIT 20;
6. Das Problem mit Seitenzahlen und direktem Sprung zu Seite N
Ein haeufig genannter Nachteil von Keyset-Pagination ist, dass sie keinen direkten Sprung zu einer beliebigen Seitenzahl unterstuetzt, weil jede Seite den Cursor der vorherigen Seite voraussetzt. OFFSET erlaubt dagegen theoretisch, direkt zu Seite 47 zu springen, ohne die vorherigen 46 Seiten gesehen zu haben, auch wenn das intern trotzdem alle uebersprungenen Zeilen liest.
In der Praxis ist dieser Nachteil kleiner als er zunaechst wirkt. Die meisten Nutzer klicken sich sequenziell durch Seiten oder nutzen Infinite Scroll, beides passt exzellent zu Keyset-Pagination. Fuer die seltenen Faelle, in denen ein direkter Sprung zu einer bestimmten Seite tatsaechlich noetig ist, etwa in einer Admin-Oberflaeche mit Seitenzahl-Eingabefeld, bieten sich hybride Loesungen an, die im naechsten Abschnitt behandelt werden, oder eine bewusste Einschraenkung auf die ersten paar hundert Seiten, waehrend darueber hinaus nur sequenzielle Navigation erlaubt wird.
Fuer Suchmaschinen-Crawler und API-Konsumenten, die systematisch alle Daten abrufen wollen, ist der fehlende Direktsprung ohnehin irrelevant, weil diese Anwendungsfaelle naturgemaess sequenziell durch die gesamte Ergebnismenge iterieren. Genau hier zeigt Keyset-Pagination ihren groessten Vorteil, weil die konstante Antwortzeit ueber die gesamte Iteration erhalten bleibt, waehrend OFFSET bei jedem weiteren Schritt langsamer wuerde.
7. Konsistenz bei gleichzeitigen Schreibvorgaengen
Ein subtiler, aber praktisch relevanter Vorteil von Keyset-Pagination betrifft die Konsistenz bei gleichzeitigen Schreibvorgaengen. Wird waehrend des Durchblaetterns mit OFFSET eine neue Zeile eingefuegt, die vor der aktuellen Position in der Sortierreihenfolge liegt, verschiebt sich der Offset aller nachfolgenden Zeilen um eins. Das Resultat: Eine Zeile wird uebersprungen oder eine Zeile erscheint doppelt auf zwei aufeinanderfolgenden Seiten, je nachdem, ob eingefuegt oder geloescht wurde.
Keyset-Pagination ist gegen dieses Problem strukturell robuster, weil der Cursor auf einem konkreten, bereits gesehenen Wert basiert, nicht auf einer relativen Position. Wird eine neue Zeile eingefuegt, die nach dem aktuellen Cursor-Wert sortiert wird, hat das keinen Einfluss auf die naechste Seite, weil die WHERE-Bedingung weiterhin korrekt alle Zeilen nach dem Cursor liefert, unabhaengig davon, wie viele Zeilen zwischenzeitlich davor eingefuegt wurden. Wird eine Zeile vor dem Cursor eingefuegt, betrifft das ebenfalls nicht die bereits ausgelieferten oder noch ausstehenden Seiten.
Diese Eigenschaft macht Keyset-Pagination besonders geeignet fuer Datenstroeme mit hoher Schreibfrequenz, etwa Activity-Feeds, Log-Auswertungen oder Bestelllisten in einem aktiven E-Commerce-System, wo OFFSET-basierte Pagination durch parallele Schreibvorgaenge regelmaessig sichtbare Inkonsistenzen fuer Endnutzer erzeugen wuerde.
8. Hybride Ansaetze und wann OFFSET noch vertretbar ist
Trotz aller Vorteile von Keyset-Pagination ist OFFSET nicht grundsaetzlich falsch. Fuer kleine Tabellen mit wenigen tausend Zeilen, bei denen selbst die tiefste Seite nur wenige tausend Zeilen ueberspringt, ist der Performance-Unterschied kaum messbar, und die Einfachheit von OFFSET, insbesondere die Unterstuetzung fuer direkte Seitensprünge, kann den zusaetzlichen Implementierungsaufwand von Keyset-Pagination nicht rechtfertigen.
Ein bewaehrter hybrider Ansatz kombiniert beide Techniken: Fuer die ersten paar Dutzend Seiten, wo Nutzer typischerweise direkt navigieren wollen, nutzt man OFFSET, weil der Performance-Nachteil dort vernachlaessigbar bleibt. Ab einer bestimmten Tiefe wechselt die Anwendung automatisch zu Keyset-Pagination, oder bietet gar keine Seitenzahlen mehr an, sondern nur noch "Weiter"-Navigation. Diese Kombination liefert die Benutzerfreundlichkeit von Seitenzahlen fuer die haeufig genutzten ersten Seiten und die Skalierbarkeit von Keyset-Pagination fuer tiefes Blaettern.
Fuer neue Systeme, insbesondere APIs, die von Anfang an fuer grosse und wachsende Datenmengen ausgelegt sind, lohnt es sich meist, direkt mit Keyset-Pagination zu starten, statt spaeter eine funktionierende OFFSET-Implementierung migrieren zu muessen, wenn die Datenmenge waechst und Performance-Probleme sichtbar werden.
-- Hybrid approach: OFFSET for shallow pages (page <= 50), keyset beyond that
-- Shallow page: simple OFFSET is fine, the cost is negligible
SELECT product_id, product_name, price
FROM products
ORDER BY product_id
LIMIT 20 OFFSET 980; -- page 50
-- Deep page: switch to keyset pagination using the last seen cursor
SELECT product_id, product_name, price
FROM products
WHERE product_id > 20450 -- cursor carried over from page 50
ORDER BY product_id
LIMIT 20;
9. OFFSET und Keyset im direkten Vergleich
Die folgende Tabelle fasst die wichtigsten Unterschiede zwischen OFFSET-Pagination und Keyset-Pagination zusammen.
| Kriterium | OFFSET-Pagination | Keyset-Pagination |
|---|---|---|
| Antwortzeit bei tiefen Seiten | Waechst linear mit der Seitentiefe | Konstant, unabhaengig von der Tiefe |
| Direkter Sprung zu Seite N | Unterstuetzt | Nicht direkt moeglich |
| Konsistenz bei Schreibvorgaengen | Anfaellig fuer Duplikate und Luecken | Robust gegen parallele Aenderungen |
| Implementierungsaufwand | Gering | Mittel, insbesondere bei Mehrspalten-Sortierung |
| Ideal fuer | Kleine Tabellen, Admin-Oberflaechen mit Seitenzahlen | Infinite Scroll, APIs, grosse und wachsende Datenmengen |
Diese Gegenueberstellung macht deutlich, dass es keine universell richtige Antwort gibt, sondern eine bewusste Entscheidung basierend auf Datenmenge, Nutzungsmuster und Anforderungen an direkte Seitennavigation. Fuer die meisten wachsenden Anwendungen ueberwiegen jedoch die Vorteile von Keyset-Pagination deutlich, sobald das Datenvolumen ueber wenige tausend Zeilen hinauswaechst.
10. Zusammenfassung
OFFSET-basierte Pagination ist einfach zu implementieren, skaliert aber strukturell schlecht, weil die Datenbank bei jeder Seite alle vorherigen Zeilen lesen und verwerfen muss, was zu linear wachsenden Antwortzeiten fuehrt. Keyset-Pagination loest dieses Problem, indem sie den Seitenversatz durch einen Vergleich mit dem letzten gesehenen Wert ersetzt, was konstante Antwortzeiten unabhaengig von der Seitentiefe ermoeglicht, vorausgesetzt die Sortierreihenfolge ist eindeutig und durch einen passenden Index unterstuetzt.
Die Wahl zwischen beiden Strategien haengt vom konkreten Anwendungsfall ab: Kleine, statische Datenmengen mit Bedarf an direkter Seitennavigation vertragen OFFSET problemlos, waehrend wachsende Datenmengen, Infinite-Scroll-Oberflaechen und APIs mit systematischer Iteration klar von Keyset-Pagination profitieren. Der zusaetzliche Implementierungsaufwand, insbesondere bei mehrspaltiger Sortierung, zahlt sich bei jedem System aus, das langfristig wachsen soll, bereits fruehzeitig aus.
Pagination-Strategien, das Wichtigste auf einen Blick
OFFSET waechst linear
Jede uebersprungene Zeile muss die Datenbank tatsaechlich lesen und verwerfen.
Keyset bleibt konstant
WHERE id > last_seen_id springt direkt ueber den Index zur richtigen Position.
Stabile Sortierung ist Pflicht
Immer nach eindeutiger Spalte oder Spalte plus Primary Key als Tie-Breaker sortieren.
Hybrid ist oft die beste Wahl
OFFSET fuer die ersten Seiten, Keyset-Pagination fuer tiefes Blaettern und APIs.