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 a WHERE clause.
  • 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 of find).
  • $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:

  1. Push $match and $limit as early as possible – this reduces the volume of data that later stages must handle.
  2. Project only needed fields before expensive operations like $group or $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:

  1. Equality – fields used with exact matches ($eq, $in) first.
  2. Sort – fields that appear in a sort() clause next.
  3. 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 be IXSCAN (index scan) rather than COLLSCAN.
  • totalDocsExamined vs. 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 find for straightforward filters; reserve aggregate for 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 for IXSCAN stages and low totalDocsExamined/nReturned ratios.
  • In sharded environments, include the shard key whenever possible to avoid full cluster scans.
  • Adjust readConcern, readPreference, and writeConcern based 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.