Skip og limit: Offset-sideinddeling
De lærende vil implementere traditionel sideinddeling med sidenumre ved hjælp af skip() og limit() og måle omkostningerne ved dette på store samlinger.
Skip og limit: Offset-sideinddeling er en gratis MongoDB Academy-lektion på CoddyKit. Dette er lektion 2 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i MongoDB Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. MongoDB Academy-kurset indeholder 4 lektioner i alt.
Hvad er offset-sideopdeling?
Offset-sideopdeling – også kaldet sideopdeling efter sidetal – opdeler resultater i sider med fast størrelse og bruger et sidetal til at bestemme, hvor langt inde i resultatsættet der skal startes. Side 1 viser element 1-20, side 2 viser element 21-40 og så videre. I MongoDB implementeres dette med skip() til at springe over tidligere resultater og limit() til at begrænse, hvor mange dokumenter der returneres pr. side.
Brug af skip() og limit()
limit(N) fortæller cursoren, at den højst skal returnere N dokumenter. skip(N) fortæller MongoDB, at de første N dokumenter skal springes over, før resultater returneres. Sammen implementerer de sideopdeling efter sidetal: Hent side P med S elementer pr. side ved at bruge skip((P-1)*S) og 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);Optælling af det samlede antal sider
Offset-sideopdeling kræver normalt et samlet antal dokumenter for at kunne vise sidetal i brugergrænsefladen. Brug countDocuments(filter) til at tælle matchende dokumenter, før sideopdelingen anvendes. Kør optællingen og forespørgslen med sideopdeling parallelt for at undgå ekstra ventetid. Det samlede antal dokumenter divideret med sidestørrelsen (afrundet op) giver det samlede antal sider.
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 skjulte omkostning ved skip()
MongoDB implementerer skip() ved at gennemsøge og forkaste de første N dokumenter. Selv med et indeks skal MongoDB gennemgå og tælle N indeksindgange, før resultaterne returneres. På side 1 er skip 0 – det er hurtigt. På side 500 med 20 elementer pr. side er skip 9980 – MongoDB skal gennemgå næsten 10.000 indgange bare for at finde ud af, hvor side 500 begynder. Dette er det centrale ydeevneproblem ved offset-sideopdeling.
O(skip + limit)-kompleksitet for forespørgsler
Tiden til at udføre en forespørgsel med skip/limit vokser lineært med mængden, der springes over. Omkostningen ved forespørgslen er O(skip + limit) – serveren skal undersøge skip-dokumenter, før den returnerer limit-dokumenter. For side 1 er dette O(20); for side 1000 med 20 elementer pr. side er det O(20020). Når brugere navigerer til højere sidetal, bliver forespørgslerne gradvist langsommere og går ofte fra millisekunder til sekunder i store collections.
Hvornår offset-sideopdeling er acceptabel
På trods af sine ydeevnebegrænsninger er offset-sideopdeling acceptabel i disse scenarier: (1) collectionen har færre end nogle få tusinde dokumenter; (2) brugere navigerer sjældent længere end de første få sider; (3) funktionen kræver, at man kan springe direkte til et sidetal (f.eks. 'gå til side 47'). Mange administrationspaneler og søgeresultater med få sider passer til denne profil. Brug keyset-sideopdeling til uendelig rulning eller store datasæt.
Implementering af et API-slutpunkt med offset-sideopdeling
Et typisk REST-listeslutpunkt accepterer forespørgselsparametrene page og limit, validerer dem og anvender derefter skip/limit. Sæt altid en øvre grænse for limit for at forhindre klienter i at anmode om tusindvis af dokumenter i et enkelt kald, hvilket ville opbruge serverens hukommelse.
// 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 });
}Problemer med datakonsistens ved offset-sideopdeling
Offset-sideopdeling har et subtilt korrekthedsproblem: Hvis dokumenter indsættes eller slettes mellem forespørgsler efter sider, kan elementer skifte position i det sorterede resultatsæt. Et dokument, der indsættes mellem side 1 og side 2, skubber alle efterfølgende dokumenter frem, så et element kan optræde på begge sider (dublet) eller helt blive sprunget over. Dette kaldes problemet med sideforskydning og er iboende i offset-sideopdeling.
Estimeret kontra nøjagtig optælling
For meget store collections kan countDocuments(filter) være langsom, fordi den gennemsøger indekset. Et alternativ er estimatedDocumentCount(), som er O(1), men tæller alle dokumenter i collectionen uden at anvende et filter. I enkle tilfælde, hvor du vil have totalen uden filtrering, er den estimerede optælling meget hurtigere. Ved filtrerede optællinger i store collections kan du overveje at cache optællingen eller bruge Atlas's facetterede søgning.
// 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 });Kombination af skip/limit med projektioner
Kombinér altid sideopdeling med en præcis projektion for listeslutpunkter. Hvis du henter alle felter under sideopdelingen, modarbejder det formålet: Du betaler stadig for at overføre fulde dokumentdata for hver side. En projektion, der kun returnerer opsummeringsfelter (navn, pris, miniaturebillede), reducerer båndbredden med 80-95 % sammenlignet med at hente fulde dokumenter og gør sideopdeling mulig ved større sideforskydninger.
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();Hvornår du skal skifte til keyset-sideopdeling
Skift fra offset- til keyset-sideopdeling (cursor-sideopdeling), når: brugere ruller uendeligt gennem resultater (der er ikke brug for sidetal), collectionen har mere end 100.000 dokumenter, indlæsningstiden for sider stiger mærkbart ved højere sidetal, eller data ændres ofte mellem forespørgsler efter sider. Keyset-sideopdeling er altid O(log n), uanset positionen i resultatsættet, fordi den bruger en intervalforespørgsel på et indekseret felt i stedet for skip.
Hurtigt tjek
Test din forståelse af begreberne MongoDB og NoSQL-databaser fra denne lektion.
Opsummering af lektionen
I denne lektion lærte du, at offset-sideopdeling bruger skip((page-1)*size) og limit(size) til at hente en side, at skip() gennemsøger og forkaster dokumenter, så dybe sider gradvist bliver langsommere, og at offset-sideopdeling er acceptabel for små collections eller få sider, men keyset-sideopdeling er bedre i stor skala. Dernæst implementerer vi keyset-sideopdeling med intervalforespørgsler for ensartet O(log n)-ydeevne.
Lær JavaScript med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 30
- Lektioner
- 120
Ofte stillede spørgsmål
Er lektionen “Skip og limit: Offset-sideinddeling” gratis?
Ja — hele teksten til “Skip og limit: Offset-sideinddeling” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af MongoDB Academy-kurset, skal du opgradere til CoddyKit PRO. MongoDB Academy-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Skip og limit: Offset-sideinddeling”?
De lærende vil implementere traditionel sideinddeling med sidenumre ved hjælp af skip() og limit() og måle omkostningerne ved dette på store samlinger. Du øver dig i MongoDB Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på MongoDB Academy?
Der kræves ingen tidligere erfaring. MongoDB Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 2 af 4.
Hvor lang tid tager lektionen “Skip og limit: Offset-sideinddeling”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne MongoDB Academy-lektion?
Ja. Alle MongoDB Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Sortering med sort() og flere nøgler
- Skip og limit: Offset-sideinddeling
- Keyset-sideinddeling med intervalforespørgsler
- Kombination af sortering, skip, limit og projektioner