Overview of the MongoDB Query System
MongoDB stores data as BSON documents, and its query language mirrors that document‑oriented model. Rather than constructing SQL strings, you build JSON‑like criteria that the server evaluates directly. The core entry point is the find method, which returns a cursor that can be further refined with sorting, paging, and projection.
db.collection.find(filter, projection)
filter– a document that specifies the conditions a record must satisfy, analogous to aWHEREclause.projection– a document that lists the fields to include or exclude in the result set.
The method never returns the entire result set at once; it hands you a cursor that streams documents as you iterate.
db.users.find(
{ age: { $gte: 18 }, status: "active" },
{ name: 1, email: 1, _id: 0 }
)
The example above selects users who are at least 18 years old and have an "active" status, returning only their name and email fields.
Querying Nested Documents
MongoDB lets you address fields inside embedded documents using dot notation:
db.users.find({ "address.city": "Beijing" })
Common Query Operators
MongoDB’s expressive power comes from a rich set of operators, grouped by purpose.
Comparison
$eq,$ne,$gt,$gte,$lt,$lte$in,$nin
Logical
$and,$or,$not,$nor
Element
$exists,$type
Array
$all,$elemMatch,$size
Evaluation
$regex,$expr,$jsonSchema
Example – combining logical and comparison operators
db.orders.find({
$and: [
{ total: { $gt: 100 } },
{ $or: [{ status: "paid" }, { status: "shipped" }] }
]
})
Example – array matching with $elemMatch
db.products.find({
tags: { $all: ["mongodb", "database"] },
items: {
$elemMatch: { qty: { $gte: 10 }, price: { $lt: 20 } }
}
})
$elemMatch ensures that a single array element satisfies both conditions, preventing false positives that could arise when the criteria match different elements.
Working with Cursors
A cursor supports a fluent API that lets you shape the result set before materializing it.
| Method | Purpose |
|---|---|
sort() |
Order documents by one or more fields |
limit() |
Restrict the number of returned documents |
skip() |
Omit a specified number of documents (useful for pagination) |
project() |
Alias for the second argument of find, applied later in the chain |
count() |
Return the total number of documents that match the filter |
explain() |
Show the query execution plan |
const cursor = db.users
.find({ city: "Beijing" })
.sort({ age: -1 })
.skip(20)
.limit(10);
cursor.forEach(printjson);
Cursors are lazy: the server only fetches data when you iterate (forEach, toArray(), etc.). For large collections, avoid toArray() and prefer streaming with forEach or manual batch processing.
The Aggregation Pipeline
When you need more than simple filtering—such as grouping, reshaping, or joining—MongoDB offers the aggregation pipeline. A pipeline is an ordered list of stages, each transforming the stream of documents.
Typical stages include:
$match– filter documents (the aggregation counterpart offind).$project– reshape documents, compute new fields, or drop existing ones.$group– aggregate data by a key, applying accumulator operators ($sum,$avg,$push, etc.).$sort– order the intermediate results.$limit/$skip– paginate.$lookup– perform a left‑outer join with another collection.$unwind– deconstruct an array field into separate documents.
Example – top‑spending paid customers
db.orders.aggregate([
{ $match: { status: "paid" } },
{ $group: { _id: "$userId", total: { $sum: "$amount" } } },
{ $sort: { total: -1 } },
{ $limit: 5 }
])
Performance tips:
- Push
$matchand$limitas early as possible – this reduces the volume of data that later stages must handle. - Project only needed fields before expensive operations like
$groupor$lookup.
Indexes and Query Planning
Indexes are the primary lever for query speed. MongoDB supports several index types:
- Single‑field – one field per index.
- Compound – multiple fields in a defined order.
- Multikey – indexes array elements.
- Text – full‑text search.
- Geospatial – location‑based queries.
When building a compound index, the field order matters. The ESR guideline helps decide the sequence:
- Equality – fields used with exact matches (
$eq,$in) first. - Sort – fields that appear in a
sort()clause next. - Range – fields used with range operators (
$gt,$lt,$gte,$lte) last.
db.users.createIndex({ city: 1, age: -1 })
To verify that a query uses an index, run explain() with the "executionStats" verbosity:
db.users.find({ city: "Beijing", age: { $gte: 18 } })
.explain("executionStats")
Key metrics to inspect:
stage– should beIXSCAN(index scan) rather thanCOLLSCAN.totalDocsExaminedvs.nReturned– a low ratio indicates efficient index usage.keysExamined– the number of index entries examined.
If you see a collection scan (COLLSCAN), consider adding or adjusting an index to cover the filter and sort patterns.
Read/Write Concerns and Sharding
In a replica set or sharded cluster, you can tune consistency and durability with three settings:
| Setting | What it controls |
|---|---|
readConcern |
Guarantees about the freshness of data read (local, majority, etc.). |
readPreference |
Which replica members serve the read (primary, secondaryPreferred, etc.). |
writeConcern |
How many replicas must acknowledge a write before it’s considered successful (w:1, w:"majority", etc.). |
db.users.find({ city: "Beijing" })
.readConcern("majority")
.readPref("secondaryPreferred")
When a collection is sharded, the shard key determines how queries are routed. Including the shard key in the filter allows mongos to target a single shard (or a small subset). Omitting it forces a scatter‑gather operation that contacts every shard, which can dramatically increase latency.
Best‑Practice Checklist
- Prefer
findfor straightforward filters; reserveaggregatefor grouping, joining, or complex transformations. - Design indexes around the most common queries, respecting the ESR order for compound indexes.
- Always project only the fields you need; this reduces network payload and can enable index‑only scans.
- Handle large result sets with cursors, processing documents in batches rather than loading everything into memory.
- Run
explain()regularly on slow queries; look forIXSCANstages and lowtotalDocsExamined/nReturnedratios. - In sharded environments, include the shard key whenever possible to avoid full cluster scans.
- Adjust
readConcern,readPreference, andwriteConcernbased on the consistency requirements of your application.
By mastering the interplay between filters, projection, cursors, aggregation stages, and indexes, you can craft queries that are both expressive and performant across a wide range of MongoDB deployments.