Effiziente Datenpaginierung: Keyset vs. Offset
Lukas Schneider
DevOps Engineer · Leapcell

Einleitung
In der Welt der Webanwendungen und datengesteuerten Systeme ist die effiziente Anzeige großer Datensätze eine grundlegende Herausforderung. Stellen Sie sich einen Social-Media-Feed, einen E-Commerce-Produktkatalog oder einen Logdatei-Viewer vor – all diese Szenarien beinhalten das Abrufen und Präsentieren potenziell Millionen von Datensätzen für Benutzer. Während das einfache Abrufen aller Daten auf einmal unpraktisch und ressourcenintensiv ist, bietet die Paginierung die De-facto-Lösung. Die Paginierung ermöglicht es uns, riesige Datensätze in überschaubare Blöcke aufzuteilen, die Leistung zu verbessern, die Ladezeiten zu reduzieren und die Benutzererfahrung zu verbessern. Dieser Artikel wird sich mit zwei gängigen Paginierungsstrategien befassen: der Offset-Paginierung und der zunehmend bevorzugten Keyset-Paginierung, auch Cursor-Paginierung genannt, und ihre zugrunde liegenden Prinzipien, Implementierungsdetails und praktischen Auswirkungen für den Aufbau skalierbarer und reaktionsfähiger Anwendungen analysieren.
Kernkonzepte
Bevor wir uns mit den Einzelheiten jeder Paginierungsmethode befassen, lassen Sie uns einige Schlüsselbegriffe definieren, die für unsere Diskussion relevant sein werden:
- Seitengröße (Limit): Die maximale Anzahl von Datensätzen, die für eine einzelne Seite abgerufen werden sollen.
- Seitennummer: Eine Ganzzahl, die die sequentielle Reihenfolge der aktuellen Seite angibt (z. B. Seite 1, Seite 2, Seite 3). Dies ist typischerweise mit der Offset-Paginierung verbunden.
- Offset: Eine Ganzzahl, die die Anzahl der Datensätze darstellt, die vom Anfang des Datensatzes übersprungen werden sollen, bevor die gewünschte Seite abgerufen wird. Dies ist ebenfalls mit der Offset-Paginierung verknüpft.
- Cursor: Eine eindeutige Kennung (oft ein Primärschlüssel, Zeitstempel oder eine Kombination von Spalten) des letzten Datensatzes, der auf der vorherigen Seite abgerufen wurde. Dies ist zentral für die Keyset-Paginierung.
- Stabile Sortierreihenfolge: Eine konsistente und vorhersagbare Reihenfolge, in der Daten abgerufen werden. Dies ist für beide Methoden entscheidend, insbesondere aber für die Keyset-Paginierung, um korrekte und nicht wiederholende Ergebnisse über mehrere Seiten hinweg sicherzustellen. Normalerweise definiert durch eine
ORDER BY
-Klausel.
Offset-Paginierung
Die Offset-Paginierung ist vielleicht die intuitivste und am weitesten verbreitete Paginierungstechnik. Sie funktioniert, indem sie eine bestimmte Anzahl von Datensätzen (OFFSET
) überspringt und dann eine feste Anzahl von Datensätzen (LIMIT
) abruft.
Prinzip
Das Prinzip ist einfach: Um die N-te Seite zu erhalten, überspringen Sie die ersten (N-1) * LIMIT
Datensätze und rufen dann die nächsten LIMIT
Datensätze ab.
Implementierung
Betrachten Sie eine products
-Tabelle mit den Spalten id
, name
, price
, created_at
.
SELECT id, name, price FROM products ORDER BY id ASC LIMIT 10 OFFSET 0; -- Erste Seite (Seite 1, Seitengröße = 10) SELECT id, name, price FROM products ORDER BY id ASC LIMIT 10 OFFSET 10; -- Zweite Seite (Seite 2, Seitengröße = 10) SELECT id, name, price FROM products ORDER BY id ASC LIMIT 10 OFFSET 20; -- Dritte Seite (Seite 3, Seitengröße = 10)
Im API-Kontext könnte die Anfrage wie folgt aussehen: GET /products?page=2&limit=10
. Das Backend berechnet dann den OFFSET
als (page - 1) * limit
.
Vorteile
- Einfachheit: Leicht zu verstehen und zu implementieren.
- Zufälliger Zugriff: Ermöglicht Benutzern, direkt zu jeder Seitennummer zu springen (z. B. zu Seite 100).
- Anzeige der Gesamtzahl: Wenn eine Gesamtzahl der Elemente verfügbar ist, ist es einfach, "Seite X von Y" oder eine Paginierungs-UI mit nummerierten Seiten anzuzeigen.
Nachteile
- Leistungsverschlechterung bei großen Offsets: Wenn der
OFFSET
-Wert steigt, muss die Datenbank immer mehr Datensätze scannen und verwerfen, was zu erheblich langsameren Abfragezeiten führt. Dies ist der Hauptnachteil bei großen Datensätzen. - Überspringen/Duplizieren von Datensätzen: Wenn Datensätze vor dem aktuellen Offset eingefügt oder gelöscht werden, während ein Benutzer paginiert, kann der Benutzer doppelte Datensätze sehen oder einige Datensätze ganz verpassen. Dies ist als das "Phantomproblem" bekannt und kann zu einer inkonsistenten Benutzererfahrung führen.
Anwendungsfälle
- Admin-Dashboards mit relativ kleinen Datensätzen.
- Situationen, in denen gelegentliche Inkonsistenzen aufgrund von Datenänderungen akzeptabel sind.
- Anwendungen, bei denen der zufällige Seitenzugriff eine wichtige Anforderung ist und die Gesamtzahl der Datensätze nicht übermäßig groß ist.
Keyset-Paginierung (Cursor-Paginierung)
Keyset-Paginierung, auch als Cursor-Paginierung bekannt, bietet eine robustere Lösung für die Leistungsprobleme und Inkonsistenzen, mit denen die Offset-Paginierung insbesondere bei sehr großen Datensätzen konfrontiert ist.
Prinzip
Anstatt eine feste Anzahl von Zeilen zu überspringen, verwendet die Keyset-Paginierung die Werte des letzten Datensatzes der vorherigen Seite als "Cursor", um zu bestimmen, wo die nächste Seite abgerufen werden soll. Sie stützt sich auf eine eindeutige, sortierte Menge von Spalten (den "Keyset"), um den nächsten Startpunkt zu definieren.
Implementierung
Fahren wir mit unserem products
-Tabellenbeispiel fort, wobei id
ein eindeutiger und automatisch inkrementierender Primärschlüssel ist.
Um die erste Seite zu erhalten:
SELECT id, name, price FROM products ORDER BY id ASC LIMIT 10; -- Angenommen, die letzte ID in diesem Ergebnis ist 10.
Um die zweite Seite zu erhalten (nach id = 10
):
SELECT id, name, price FROM products WHERE id > 10 -- Der "Cursor" ist die letzte ID von der vorherigen Seite ORDER BY id ASC LIMIT 10;
Wenn wir eine komplexere Sortierung benötigen, z. B. nach price
und dann nach id
bei gleichen Preisen:
SELECT id, name, price FROM products ORDER BY price ASC, id ASC LIMIT 10; -- Angenommen, der letzte Datensatz dieser Seite ist {id: 7, price: 9.99}.
Um die nächste Seite zu erhalten:
SELECT id, name, price FROM products WHERE (price > 9.99) OR (price = 9.99 AND id > 7) -- Cursor basierend auf Preis UND ID ORDER BY price ASC, id ASC LIMIT 10;
Im API-Kontext könnte die Anfrage nach der nächsten Seite wie folgt aussehen: GET /products?limit=10&last_id=10
oder GET /products?limit=10&last_price=9.99&last_id=7
. Die last_id
oder die Kombination aus last_price
und last_id
fungiert als Cursor.
Vorteile
- Konsistente Leistung: Verschlechtert sich nicht mit zunehmenden "Seitenzahlen". Die
WHERE
-Klausel mit einer indizierten Spalte (wieid
oder einer Kombination für den Keyset) ermöglicht es der Datenbank, schnell zum Startpunkt zu springen, sodass die Leistung weitgehend unabhängig von der Paginierungstiefe ist. - Robustheit gegenüber Datenänderungen: Einfügungen oder Löschungen vor der aktuellen Seite beeinträchtigen nicht die Integrität der aktuellen Seite oder nachfolgender Seiten. Benutzer sehen keine doppelten Datensätze und verpassen keine Datensätze, die sie hätten sehen sollen, da der Cursor auf einen bestimmten Punkt im sortierten Datensatz zeigt.
- Skalierbarkeit: Sehr gut geeignet für sehr große Datensätze und Hochverkehrsanwendungen.
Nachteile
- Kein zufälliger Zugriff: Benutzer können nicht direkt zu einer beliebigen Seitenzahl springen. Sie können nur "weiter" oder "zurück" navigieren (durch Umkehrung der
WHERE
-Klausel und der Sortierreihenfolge). - Keine Gesamtzahl: Es ist schwierig, "Seite X von Y" anzuzeigen, ohne eine separate, potenziell teure
COUNT(*)
-Abfrage. - Erfordert stabile Sortierreihenfolge: Eine konsistente und eindeutige Sortierreihenfolge (der Keyset) ist zwingend erforderlich. Wenn die Sortierreihenfolge nicht eindeutig ist, müssen Sie eine Spalte zur Aufschlüsselung von Gleichständen (wie den Primärschlüssel) zum Keyset hinzufügen.
- Komplexere Implementierung: Kann schwieriger zu implementieren sein, insbesondere bei zusammengesetzten Keysets oder bei der Handhabung der Funktionalität für die "vorherige Seite".
Anwendungsfälle
- Social-Media-Feeds (z. B. Twitter, Facebook), bei denen Benutzer unendlich scrollen.
- Log-Explorationstools, bei denen ständig neue Daten hinzugefügt werden.
- E-Commerce-Produktlisten, bei denen Leistung und Konsistenz oberste Priorität haben.
- Jede Anwendung, die mit sehr großen, häufig aktualisierten Datensätzen zu tun hat, bei denen Benutzer hauptsächlich sequenziell navigieren.
Fazit
Sowohl die Offset-Paginierung als auch die Keyset-Paginierung dienen dazu, große Datensätze in überschaubare Blöcke aufzuteilen, aber sie eignen sich für unterschiedliche Szenarien. Die Offset-Paginierung bietet Einfachheit und direkten Zugriff auf Seiten, wodurch sie sich für kleinere Datensätze oder bestimmte administrative Schnittstellen eignet, bei denen die Leistung auf tiefen Seiten nicht kritisch ist. Ihre Leistung verschlechtert sich jedoch mit zunehmenden Offsets und sie ist anfällig für Dateninkonsistenzen bei gleichzeitigen Änderungen.
Die Keyset-Paginierung hingegen bietet eine überlegene Leistungskonsistenz und Robustheit gegenüber Datenänderungen und ist daher die bevorzugte Wahl für große, dynamische Datensätze und benutzerorientierte Anwendungen, die hohe Skalierbarkeit und eine nahtlose Benutzererfahrung erfordern. Obwohl sie den zufälligen Seitenzugriff und die Einfachheit opfert, überwiegen ihre Vorteile in Bezug auf Effizienz und Datenintegrität in modernen, datenintensiven Umgebungen oft diese Nachteile. Letztendlich hängt die Wahl zwischen diesen beiden Methoden von den spezifischen Projektanforderungen, der Datensatzgröße und den erwarteten Benutz interaktionsmustern ab. Für die meisten modernen Webanwendungen, die mit erheblichen Datenmengen arbeiten, führt die Keyset-Paginierung zu einer weitaus performanteren und zuverlässigeren Benutzererfahrung.