Paginierung ist für jede API, die Listen zurückgibt, unerlässlich – ohne sie führt eine Abfrage, die Millionen von Zeilen zurückgibt, zum Absturz Ihres Servers und überlastet Clients. Es gibt jedoch mehrere Paginierungsstrategien mit sehr unterschiedlichen Leistungsmerkmalen. In diesem Leitfaden werden sie alle behandelt und erläutert, wann sie jeweils zu verwenden sind.
📋 Table of Contents
- Warum Paginierung wichtig ist
- Strategie 1: Offset-/Limit-Paginierung (einfach)
- Strategie 2: Cursor-Paginierung (skalierbar)
- Strategie 3: Keyset-Paginierung (beste Leistung)
- Vergleich: Was ist zu verwenden
- Konsistentes Antwortformat
- Leistungstipps
- gibt Häufig gestellte Fragen
- keine unbegrenzten Daten anfordern können Fazit
Warum Paginierung wichtig ist
- Leistung: Die Rückgabe aller Zeilen ist langsam und speicherintensiv
- Bandbreite: Kunden benötigen nicht Tausende von Datensätzen auf einmal
- Datenbanklast: Begrenzte Abfragen schützen Ihre Datenbank
- Benutzererfahrung: Das Laden von Daten in Seiten ist schneller und sauberer
Strategie 1: Offset-/Limit-Paginierung (einfach)
-- The classic approach: OFFSET and LIMIT
SELECT * FROM products
ORDER BY created_at DESC
LIMIT 20 OFFSET 40; -- page 3 (skip 40, take 20)
// Express endpoint
app.get('/products', async (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 20;
const offset = (page - 1) * limit;
const products = await db.query(
'SELECT * FROM products ORDER BY created_at DESC LIMIT $1 OFFSET $2',
[limit, offset]
);
const total = await db.query('SELECT COUNT(*) FROM products');
res.json({
data: products.rows,
pagination: {
page,
limit,
total: total.rows[0].count,
totalPages: Math.ceil(total.rows[0].count / limit),
}
});
});
Vorteile: Einfach, unterstützt das Springen zu jeder Seite und zeigt die Gesamtzahl an.
Nachteile: Langsam bei großen Offsets (die Datenbank scannt weiterhin alle übersprungenen Zeilen) und die Ergebnisse können sich verschieben, wenn sich Daten zwischen Anforderungen ändern.
Strategie 2: Cursor-Paginierung (skalierbar)
Verwenden Sie anstelle eines Offsets einen „Cursor“ (normalerweise die ID oder den Zeitstempel des letzten Elements), um die nächste Seite abzurufen. Dies lässt sich auf Millionen von Zeilen skalieren, da die Datenbank direkt zur Position springt:
-- Fetch items AFTER a cursor (much faster than large OFFSET)
SELECT * FROM products
WHERE created_at < $1 -- cursor = last item's created_at
ORDER BY created_at DESC
LIMIT 20;
app.get('/products', async (req, res) => {
const limit = parseInt(req.query.limit) || 20;
const cursor = req.query.cursor; // the last item's timestamp/id
let query, params;
if (cursor) {
query = 'SELECT * FROM products WHERE created_at < $1 ORDER BY created_at DESC LIMIT $2';
params = [cursor, limit + 1]; // fetch one extra to check for more
} else {
query = 'SELECT * FROM products ORDER BY created_at DESC LIMIT $1';
params = [limit + 1];
}
const result = await db.query(query, params);
const hasMore = result.rows.length > limit;
const items = hasMore ? result.rows.slice(0, limit) : result.rows;
const nextCursor = hasMore ? items[items.length - 1].created_at : null;
res.json({
data: items,
pagination: { nextCursor, hasMore }
});
});
Vorteile: Schnell in jedem Maßstab (kein versetztes Scannen), stabile Ergebnisse auch bei Datenänderungen.
Nachteile: Es ist nicht möglich, zu beliebigen Seiten zu springen, keine Gesamtseitenzahl, etwas komplexer.
Strategie 3: Keyset-Paginierung (beste Leistung)
-- Keyset uses a unique, ordered column (often id) for precise positioning
SELECT * FROM products
WHERE (created_at, id) < ($1, $2) -- composite cursor handles ties
ORDER BY created_at DESC, id DESC
LIMIT 20;
Die Keyset-Paginierung verwendet einen zusammengesetzten Cursor (wie „created_at + id“), um Zeilen mit identischen Zeitstempeln zu verarbeiten. Es handelt sich um den robustesten Hochleistungsansatz, der von APIs verwendet wird, die große Datenmengen bedienen.
Vergleich: Was ist zu verwenden
| Strategie | Am besten für | Maßstab |
|---|---|---|
| Offset/Grenze | Kleine Datensätze, Admin-Benutzeroberflächen, die Seitenzahlen benötigen | Klein-mittel |
| Cursor | Feeds, unendliches Scrollen, große Datensätze | Groß |
| Schlüsselsatz | Riesige Datensätze, hohe Leistungsanforderungen | Sehr groß |
Konsistentes Antwortformat
// Offset-based response
{
"data": [ /* items */ ],
"pagination": {
"page": 3,
"limit": 20,
"total": 1543,
"totalPages": 78
}
}
// Cursor-based response
{
"data": [ /* items */ ],
"pagination": {
"nextCursor": "2026-07-20T10:30:00Z",
"hasMore": true
}
}
Leistungstipps
- Indizieren Sie Ihre Sortierspalte: Paginierungsabfragen benötigen einen Index für die Spalte ORDER BY, sonst sind sie langsam
- Vermeiden Sie COUNT(*) bei großen Tabellen: Das Zählen aller Zeilen ist kostspielig – durch die Cursor-Paginierung ist keine Gesamtzählung erforderlich
- Begrenzen Sie das Limit: Erzwingen Sie eine maximale Seitengröße (z. B. 100), damit Clients nicht alles anfordern können
- Verwenden Sie Cursor/Keyset für große Datenmengen: Bei großen Offsets verschlechtert sich die Offset-Paginierung stark – die Datenbank durchsucht alle übersprungenen Zeilen
- Holen Sie sich limit+1, um „hasMore“ zu erkennen: Fordern Sie eine zusätzliche Zeile an, um zu erfahren, ob es eine nächste Seite ohne separate Zählung
gibt Häufig gestellte Fragen
F: Offset- oder Cursor-Paginierung?
A: Offset für kleine Datensätze und Admin-UIs, die Seitenzahlen und das Springen zu bestimmten Seiten benötigen. Cursor für Feeds, unendliches Scrollen und große Datensätze, bei denen es auf Leistung und Stabilität ankommt. Der Cursor lässt sich viel besser skalieren, kann aber nicht zu beliebigen Seiten springen.
F: Warum ist meine Offset-Paginierung langsam?
A: OFFSET 100000 lässt die Datenbank 100.000 Zeilen scannen und verwerfen, bevor sie Ihre Seite zurückgibt – mit zunehmendem Offset zunehmend langsamer. Wechseln Sie zur Cursor- oder Keyset-Paginierung, die mithilfe einer indizierten Spalte direkt zur Position springt.
F: Wie gehe ich mit Datenänderungen zwischen Seiten um?
A: Durch die versetzte Paginierung können Elemente übersprungen oder dupliziert werden, wenn zwischen Anfragen Zeilen hinzugefügt/entfernt werden. Die Cursor-Paginierung ist stabil, da sie an der Position eines bestimmten Elements und nicht an einem numerischen Offset verankert ist. Verwenden Sie die Cursor-Paginierung, wenn sich Daten häufig ändern.
F: Muss ich eine Gesamtzahl zurückgeben?
A: Bei versetzter Paginierung mit Seitenzahlen normalerweise ja. Aber COUNT(*) bei großen Tabellen ist teuer. Die Cursor-Paginierung vermeidet dies vollständig (nur „hasMore“). Wenn Sie ungefähre Zählungen für große Tabellen benötigen, ziehen Sie zwischengespeicherte oder geschätzte Zählungen in Betracht.
F: Was ist eine gute Standard- und maximale Seitengröße?
A: 20–25 als Standard, wobei maximal 100 serverseitig erzwungen werden. Dadurch wird die Antwortgröße und die Anzahl der Anfragen ausgeglichen. Begrenzen Sie das Limit immer, damit Clients mit?limit=999999.
keine unbegrenzten Daten anfordern können Fazit
Die richtige Paginierung ist für jede API, die eine Liste zurückgibt, von entscheidender Bedeutung. Verwenden SieOffset/Limit für kleine Datensätze und Admin-UIs, die Seitenzahlen benötigen, Cursor-Paginierung für Feeds und große Datensätze sowie Keyset-Paginierung für massive Hochleistungsanforderungen. Der Versatz ist am einfachsten, verschlechtert sich jedoch bei großen Versätzen. Cursor und Tastensatz skalieren auf Millionen von Zeilen, indem sie mithilfe indizierter Spalten direkt zur Position springen. Indizieren Sie Ihre Sortierspalte, begrenzen Sie die Seitengröße, rufen Sie Limit+1 ab, um mehr Seiten zu erkennen, und geben Sie ein konsistentes Antwortformat zurück. Durch die Auswahl der richtigen Strategie für Ihre Datenskala bleibt Ihre API schnell und Ihre Datenbank gesund, auch wenn Ihre Daten wachsen.
🔗 Share this article
✍️ Leave a Comment