Skip och limit: offset-baserad paginering
Ni implementerar traditionell paginering med sidnummer med skip() och limit() och mäter prestandakostnaden för stora samlingar.
Skip och limit: offset-baserad paginering är en gratis lektion i MongoDB Academy på CoddyKit. Detta är lektion 2 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för MongoDB Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i MongoDB Academy innehåller totalt 4 lektioner.
Vad är offset-paginering?
Offset-paginering – även kallad sidnummerbaserad paginering – delar upp resultaten i sidor med fast storlek och använder ett sidnummer för att avgöra hur långt in i resultatmängden sökningen ska börja. Sida 1 visar objekten 1–20, sida 2 visar objekten 21–40 och så vidare. I MongoDB implementeras detta med skip() för att hoppa över tidigare resultat och limit() för att begränsa hur många dokument som returneras per sida.
Använda skip() och limit()
limit(N) anger att cursorn ska returnera högst N dokument. skip(N) anger att MongoDB ska hoppa över de första N dokumenten innan några resultat returneras. Tillsammans implementerar de sidnummerbaserad paginering: för att hämta sida P med S objekt per sida använder ni skip((P-1)*S) och limit(S).
const PAGE = 3;
const PAGE_SIZE = 20;
// Page 3 of 20 results per page
const products = await db.collection('products')
.find({ isActive: true })
.sort({ createdAt: -1 })
.skip((PAGE - 1) * PAGE_SIZE) // skip 40 docs (pages 1 and 2)
.limit(PAGE_SIZE) // return next 20
.toArray();
console.log('Page 3 results:', products.length);Räkna det totala antalet sidor
Offset-paginering kräver vanligtvis ett totalt dokumentantal för att sidnummer ska kunna visas i användargränssnittet. Använd countDocuments(filter) för att räkna matchande dokument innan pagineringen tillämpas. Kör räkningen och den paginerade frågan parallellt för att undvika extra fördröjning. Det totala antalet dokument dividerat med sidstorleken (avrundat uppåt) ger det totala antalet sidor.
const filter = { isActive: true };
const [total, results] = await Promise.all([
db.collection('products').countDocuments(filter),
db.collection('products')
.find(filter)
.sort({ createdAt: -1 })
.skip((PAGE - 1) * PAGE_SIZE)
.limit(PAGE_SIZE)
.toArray()
]);
const totalPages = Math.ceil(total / PAGE_SIZE);
console.log('Total:', total, 'Pages:', totalPages);Den dolda kostnaden för skip()
MongoDB implementerar skip() genom att skanna och kasta bort de första N dokumenten. Även med ett index måste MongoDB gå igenom och räkna N indexposter innan resultaten returneras. På sida 1 är skip 0 – snabbt. På sida 500 med 20 objekt per sida är skip 9980 – MongoDB måste gå igenom nästan 10 000 poster bara för att hitta var sida 500 börjar. Detta är det grundläggande prestandaproblemet med offset-paginering.
Frågekomplexiteten O(skip + limit)
Tiden det tar att köra en skip/limit-fråga växer linjärt med mängden som hoppas över. Frågekostnaden är O(skip + limit) – servern måste undersöka skip dokument innan den returnerar limit dokument. För sida 1 är detta O(20); för sida 1000 med 20 objekt per sida är det O(20020). När användare navigerar till högre sidnummer blir frågorna successivt långsammare och går ofta från millisekunder till sekunder i stora samlingar.
När offset-paginering är acceptabel
Trots sina prestandabegränsningar är offset-paginering acceptabel i följande scenarier: (1) samlingen innehåller färre än några tusen dokument; (2) användare navigerar sällan längre än de första sidorna; (3) funktionen kräver att användaren kan hoppa direkt till ett sidnummer (t.ex. ”gå till sida 47”). Många administrationspaneler och sökresultat med få sidnivåer passar in på denna beskrivning. Använd keyset-paginering för oändlig rullning eller stora datamängder.
Implementera en API-endpoint med offset-paginering
En typisk REST-endpoint för listor tar emot frågeparametrarna page och limit, validerar dem och tillämpar skip/limit därefter. Sätt alltid ett tak för den maximala limit-inställningen för att förhindra att klienter begär tusentals dokument i ett enda anrop, vilket skulle kunna tömma serverns minne.
// Express route: GET /api/products?page=2&limit=20
async function listProducts(req, res) {
const page = Math.max(1, parseInt(req.query.page) || 1);
const limit = Math.min(100, parseInt(req.query.limit) || 20); // cap at 100
const skip = (page - 1) * limit;
const [total, items] = await Promise.all([
Product.countDocuments({ isActive: true }),
Product.find({ isActive: true }).sort('-createdAt').skip(skip).limit(limit).lean()
]);
res.json({ page, limit, total, totalPages: Math.ceil(total / limit), items });
}Datakonsistensproblem med offset-paginering
Offset-paginering har ett subtilt korrekthetsproblem: om dokument läggs till eller tas bort mellan sidförfrågningar kan objekten förskjutas i den sorterade resultatmängden. Ett dokument som läggs till mellan sida 1 och sida 2 flyttar alla efterföljande dokument framåt, vilket gör att ett objekt visas på båda sidorna (dupliceras) eller hoppas över helt. Detta kallas problemet med sidförskjutning och är inneboende i offset-paginering.
Uppskattat eller exakt antal
För mycket stora samlingar kan countDocuments(filter) vara långsam eftersom den skannar indexet. Ett alternativ är estimatedDocumentCount(), som har komplexiteten O(1) men räknar alla dokument i samlingen utan att tillämpa något filter. I enkla fall där ni vill ha totalantalet utan filtrering är den uppskattade räkningen mycket snabbare. För filtrerade räkningar i stora samlingar kan ni överväga att cacha antalet eller använda Atlas facetterade sökning.
// O(1) but no filter support
const approxTotal = await db.collection('products').estimatedDocumentCount();
// Exact count with filter (slower on large collections)
const exactTotal = await db.collection('products').countDocuments({ isActive: true });Kombinera skip/limit med projektioner
Kombinera alltid paginering med en begränsad projektion för list-endpoints. Att hämta alla fält vid paginering motverkar syftet: ni betalar fortfarande för att överföra fullständiga dokument för varje sida. En projektion som endast returnerar sammanfattningsfält (namn, pris, miniatyrbild) minskar bandbredden med 80–95 % jämfört med att hämta fullständiga dokument, vilket gör paginering genomförbar även vid större offsetvärden.
const SUMMARY = { _id: 1, name: 1, price: 1, thumbnailUrl: 1, rating: 1 };
const items = await db.collection('products')
.find({ isActive: true })
.projection(SUMMARY)
.sort({ rating: -1 })
.skip((PAGE - 1) * PAGE_SIZE)
.limit(PAGE_SIZE)
.toArray();När det är dags att byta till keyset-paginering
Byt från offset- till keyset-paginering (cursor-paginering) när: användare rullar oändligt genom resultaten (inga sidnummer behövs), samlingen innehåller fler än 100 000 dokument, sidladdningstiderna ökar märkbart för högre sidnummer eller data ändras ofta mellan sidförfrågningar. Keyset-paginering har alltid komplexiteten O(log n), oavsett position i resultatmängden, eftersom den använder en intervallfråga på ett indexerat fält i stället för skip.
Snabbkontroll
Testa er förståelse av begreppen MongoDB och NoSQL-databaser från den här lektionen.
Sammanfattning av lektionen
I den här lektionen lärde ni er att offset-paginering använder skip((page-1)*size) och limit(size) för att hämta en sida, att skip() skannar och kastar bort dokument så att djupa sidor blir successivt långsammare och att offset-paginering fungerar för små samlingar eller få sidnivåer, medan keyset-paginering är bättre i stor skala. Nästa avsnitt handlar om att implementera keyset-paginering med intervallfrågor för konsekvent prestanda på O(log n).
Lär dig JavaScript med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 30
- Lektioner
- 120
Vanliga frågor
Är lektionen ”Skip och limit: offset-baserad paginering” gratis?
Ja – hela texten till ”Skip och limit: offset-baserad paginering” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i MongoDB Academy, kan Ni uppgradera till CoddyKit PRO. Kursen i MongoDB Academy innehåller totalt 4 lektioner.
Vad lär jag mig i ”Skip och limit: offset-baserad paginering”?
Ni implementerar traditionell paginering med sidnummer med skip() och limit() och mäter prestandakostnaden för stora samlingar. Ni övar på MongoDB Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig MongoDB Academy?
Du behöver inga förkunskaper. Utbildningen i MongoDB Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 2 av 4.
Hur lång tid tar lektionen ”Skip och limit: offset-baserad paginering”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här MongoDB Academy-lektionen?
Ja. Varje MongoDB Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- Sortering med sort() och flera nycklar
- Skip och limit: offset-baserad paginering
- Keyset-paginering med intervallfrågor
- Kombinera sort, skip, limit och projektioner