findOne kontra find: förklarade cursors
Hämta dokument med findOne och iterera över en find-cursor, och förstå hur MongoDB strömmar stora resultatmängder.
findOne kontra find: förklarade cursors ä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.
Två sätt att läsa dokument
MongoDB tillhandahåller två huvudsakliga metoder för att läsa dokument från en collection:
- findOne(filter, projection) — hämtar det första dokumentet som matchar filtret och returnerar det som ett vanligt dokumentobjekt (eller
nullom inget matchar) - find(filter, projection) — hämtar alla matchande dokument och returnerar en cursor, det vill säga en lat iterator som strömmar resultat från servern, en batch i taget
Att förstå när varje metod ska användas och hur cursors fungerar är grundläggande för att skriva effektiva MongoDB-frågor.
findOne: enkelt och direkt
findOne() är det enklaste sättet att hämta ett enskilt dokument. Metoden returnerar det första dokumentet som matchar filtret eller null om inget dokument matchar. Om flera dokument matchar returnerar MongoDB det som den först hittar i sin interna ordning. Lägg till .sort() före anropet om du behöver ett specifikt dokument.
Vanliga användningsområden för findOne är att slå upp en användare med e-postadress, hämta en produkt med SKU eller kontrollera om en post finns. Eftersom metoden returnerar ett vanligt objekt i stället för en cursor använder du resultatet direkt utan iteration.
// findOne by _id (most common lookup)
const user = await db.collection('users').findOne(
{ _id: ObjectId('64a2f3b1...') }
);
if (!user) {
throw new Error('User not found');
}
console.log(user.name); // 'Alice'
// findOne with a filter
const admin = await db.collection('users').findOne({ role: 'admin' });
// Returns ONE admin doc (undefined order), or nullVad är en cursor?
En cursor är en pekare till resultatmängden för en fråga. När du anropar find() överför MongoDB inte omedelbart alla matchande dokument till klienten. I stället öppnar servern en cursor och skickar dokument i batcher (standardvärdet är 101 dokument per batch). Klienten hämtar nästa batch först när den aktuella batchen är slut.
Den här utformningen är avgörande för effektiv minnesanvändning. Om en fråga matchar 10 miljoner dokument och du läser in alla på en gång skulle klienten krascha. Med en cursor bearbetar du dokumenten en batch i taget, så att minnesanvändningen förblir konstant oavsett resultatmängdens storlek.
// find() returns a cursor, not documents
const cursor = db.collection('orders').find({ status: 'pending' });
// No data fetched yet!
// Data flows as you iterate:
for await (const order of cursor) {
// Each iteration fetches from server in batches
console.log(order._id);
}
// Cursor is exhausted — server releases itIterera över cursors i Node.js
Cursors i Node.js-drivrutinen stöder flera iterationsmönster. Det modernaste sättet är for await...of (asynkron iteration), som hanterar backpressure och felhantering på ett tydligt sätt. Alternativ finns, bland annat cursor.toArray(), som läser in alla resultat i minnet – praktiskt men riskabelt för stora resultatmängder.
Stäng alltid cursors när du är klar om du avbryter iterationen i förtid (till exempel efter att du har hittat det du behöver). En öppen cursor använder resurser på MongoDB-servern. Använd uttryckligen cursor.close() eller förlita dig på for await...of, som stänger cursorn automatiskt när loopen slutförs eller ett fel uppstår.
// Pattern 1: async for...of (recommended)
const cursor = db.collection('products').find({ inStock: true });
for await (const product of cursor) {
await processProduct(product);
}
// Pattern 2: toArray() - loads all into memory
const products = await db.collection('products')
.find({ inStock: true }).toArray();
// Pattern 3: forEach
await cursor.forEach(product => console.log(product.name));Cursorstorlek och getMore
Internt fungerar cursor-protokollet i två faser:
- Det första kommandot
findreturnerar den första batchen (standardvärdet är 101 dokument eller 16 MB, beroende på vilket som inträffar först) - Varje efterföljande batch hämtas via kommandot
getMoremed hjälp av cursor-ID:t
Du kan anpassa batchstorleken med cursor.batchSize(n). En mindre batchstorlek minskar minnesanvändningen på båda sidor, men kräver fler nätverksomgångar. En större batchstorlek är effektivare vid stora sekventiella genomsökningar. Standardvärdet är vanligtvis optimalt – justera det endast för specifika arbetsbelastningar.
// Set a custom batch size (rarely needed)
const cursor = db.collection('logs')
.find({})
.batchSize(500);
// Count documents in a cursor without loading them
// (MongoDB 4.4+ supports .count() on cursor for backwards compat)
// Prefer countDocuments() for accurate counts:
const count = await db.collection('logs').countDocuments({});
console.log('Total logs:', count);Timeout och sessioner för cursors
Som standard upphör MongoDB-cursors efter 10 minuters inaktivitet på serversidan. Om bearbetningen av varje batch tar längre tid än så avslutas cursorn, och du får felet CursorNotFound när du försöker hämta nästa batch.
För långvariga operationer anger du noCursorTimeout: true eller använder en session för att hålla cursorn vid liv. Observera dock att noCursorTimeout håller en servercursor öppen på obestämd tid. Stäng alltid sådana cursors uttryckligen när du är klar, så undviker du resursläckor.
// Long-running cursor that won't time out
const cursor = db.collection('bigCollection').find(
{},
{ noCursorTimeout: true }
);
try {
for await (const doc of cursor) {
await slowProcessing(doc); // Takes > 10 minutes total
}
} finally {
// Always close explicitly when using noCursorTimeout
await cursor.close();
}Kedja modifierare på find()
Cursorn som returneras av find() stöder ett fluent API – du kedjar metoder för att ändra frågan innan iterationen börjar. Ordningen har betydelse för läsbarheten, men inte för körningen (MongoDB skickar alla modifierare tillsammans):
.sort({ field: 1 })— sorteringsriktning.limit(n)— maximalt antal dokument.skip(n)— hoppa över de första n resultaten.projection({ field: 1 })— välj fält.hint({ index: 1 })— tvinga fram användning av ett specifikt index.maxTimeMS(ms)— avbryt om frågan tar för lång tid
// Full chained query: filter → sort → skip → limit → projection
const page2Products = db.collection('products').find(
{ category: 'Electronics', inStock: true },
{ name: 1, price: 1, _id: 0 } // projection as 2nd arg
)
.sort({ price: -1 }) // Descending price
.skip(20) // Skip page 1 (20 items)
.limit(20) // Page size 20
.maxTimeMS(5000); // Abort if > 5sTailable cursors för capped collections
En särskild cursortyp som kallas tailable cursor fungerar endast på capped collections. Till skillnad från vanliga cursors, som stängs när alla resultat har lästs, blockerar en tailable cursor och väntar på nya dokument, ungefär som Unix-kommandot tail -f för en loggfil.
Tailable cursors var den ursprungliga mekanismen för dataströmning i realtid i MongoDB, innan Change Streams introducerades. De är fortfarande användbara för enkel loggövervakning i capped collections, där Change Streams skulle vara överdimensionerade.
// Tailable cursor on a capped collection
const tailCursor = db.collection('appLogs').find(
{},
{ tailable: true, awaitData: true }
);
// Blocks and awaits new log entries indefinitely
for await (const log of tailCursor) {
console.log('[' + log.level + '] ' + log.message);
// Prints each new log as it is inserted
}findOne eller find: välj rätt
Använd följande tumregel när du väljer mellan findOne och find:
- Använd findOne när: du förväntar dig exakt ett resultat (uppslagning med en unik nyckel), bara behöver kontrollera om något finns eller vill ha den enklaste koden för en API-endpoint som returnerar en enskild post
- Använd find när: frågan kan returnera noll, ett eller många resultat, du bygger en endpoint som returnerar en lista, behöver styra cursorn (batchSize, maxTimeMS) eller bearbetar resultat utan att läsa in allt i minnet
Undvik find({}).toArray() för stora collections – det läser in alla resultat i minnet. Bearbeta dem i stället med for await...of.
// GOOD: findOne for unique key lookup
const user = await db.collection('users').findOne({ email: 'alice@test.com' });
// GOOD: find with streaming for large sets
for await (const doc of db.collection('users').find({ active: true })) {
await sendNewsletter(doc);
}
// BAD: loading millions of docs into memory
const allUsers = await db.collection('users').find({}).toArray();
// Could OOM crash your server!Metoden explain() för cursors
Om du lägger till .explain('executionStats') i en cursor visas hur MongoDB kör frågan i stället för att dokument returneras. Resultatet visar:
winningPlan.stage:IXSCAN(ett index används) ellerCOLLSCAN(fullständig genomsökning – dåligt för stora collections)nReturned: hur många dokument som returneradestotalDocsExamined: hur många dokument MongoDB granskade för att hitta resultaten (bör ligga nära nReturned om ett index används)executionTimeMillis: den totala körningstiden
Att regelbundet köra explain() på dina viktigaste frågor är grunden för prestandajustering i MongoDB.
// Check query execution plan
const plan = await db.collection('users')
.find({ email: 'alice@test.com' })
.explain('executionStats');
console.log(plan.queryPlanner.winningPlan.stage);
// 'IXSCAN' if email is indexed, 'COLLSCAN' if not
console.log(plan.executionStats.nReturned); // 1
console.log(plan.executionStats.totalDocsExamined); // 1 (indexed) or 50000 (COLLSCAN)Konvertera ObjectId i API-svar
När findOne eller find().toArray() returnerar dokument med fält av typen ObjectId måste dessa ObjectId-värden hanteras särskilt innan de returneras i ett JSON API-svar. JSON.stringify serialiserar ett ObjectId som ett objekt {} (vilket gör att värdet går förlorat) i äldre drivrutinsversioner, eller som sin strängrepresentation i nyare versioner.
Det säkraste tillvägagångssättet är att uttryckligen anropa .toString() på alla ObjectId-fält i en mappingfunktion innan du skickar svaret till klienten. Klienterna skickar sedan tillbaka ID:t som en sträng, och på servern konverterar du det med new ObjectId(idString) innan du frågar databasen.
function toPublicDoc(doc) {
if (!doc) return null;
return {
...doc,
_id: doc._id.toString(), // ObjectId -> string for JSON
authorId: doc.authorId ? doc.authorId.toString() : null
};
}
// Usage:
const post = await db.collection('posts').findOne({ slug: 'intro' });
res.json(toPublicDoc(post));
// Client receives: { _id: '64a2f3b1c9e7...', title: '...' }Snabbkontroll
Testa dina kunskaper om begreppen MongoDB och NoSQL-databaser från den här lektionen.
Sammanfattning av lektionen
I den här lektionen har Ni lärt Er: findOne returnerar ett enda dokument direkt, medan find returnerar en cursor som strömmar resultaten stegvis för att undvika minnesproblem med stora resultatmängder. Cursorer stöder ett kedje-API—.sort(), .limit(), .skip(), .maxTimeMS()—som MongoDB skickar som en enda optimerad fråga, och explain('executionStats') visar om en fråga använder ett index (IXSCAN) eller genomsöker hela samlingen (COLLSCAN). Härnäst går vi igenom hur man frågar efter nästlade fält och arrayer med punktnotation.
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 ”findOne kontra find: förklarade cursors” gratis?
Ja – hela texten till ”findOne kontra find: förklarade cursors” 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 ”findOne kontra find: förklarade cursors”?
Hämta dokument med findOne och iterera över en find-cursor, och förstå hur MongoDB strömmar stora resultatmängder. 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 ”findOne kontra find: förklarade cursors”?
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
- insertOne och insertMany
- findOne kontra find: förklarade cursors
- Fråga efter nästlade fält och arrayer
- Läs dokument med Node.js-drivrutinen