Query optimization reduces the amount of data a query must process. Use indexes, projections, and query limits to improve performance and reduce resource consumption. Review query performance periodically as collections grow to determine when to scale.
Create Indexes to Support Queries
Indexes store field values in a separate data structure. In read operations, MongoDB searches the index instead of scanning the entire collection. In write operations, MongoDB updates both the collection and the index.
Create indexes for your most common queries. If a query searches multiple fields, create a compound index.
For example, consider the following query on the type field in the
inventory collection:
let typeValue = <someUserInput>; db.inventory.find( { type: typeValue } );
To improve performance on this query, add an index on the type field.
db.inventory.createIndex( { type: 1 } )
[1] In mongosh, use db.collection.createIndex():
To analyze query performance, see Interpret Explain Plan Results.
| [1] | For single-field indexes, the order of the index does not matter. For compound indexes, the field order impacts what queries the index supports. For details, see Compound Index Sort Order. |
Create Selective Queries
Query selectivity measures how well a query predicate filters documents and determines whether queries can use indexes effectively.
Highly selective queries match fewer documents and use indexes more effectively. For instance, an equality match on
_idis highly selective as it can match at most one document.Less selective queries match more documents and use indexes less efficiently.
For instance, the inequality operators $nin and
$ne are not very selective since they often match a large
portion of the index. As a result, in many cases, a $nin or
$ne query with an index may perform no better than a
$nin or $ne query that must scan all documents in
a collection.
The selectivity of a regular expression depends on the
expression itself. For details, see regular expression and index
use.
Project Only Necessary Data
When you need a subset of fields from documents, you can improve performance by returning only the fields you need. Projections reduce network traffic and processing time.
For example, consider the following query to return only the timestamp, title,
author, and abstract fields.
db.posts.find( {}, { timestamp : 1, title : 1, author : 1, abstract : 1} ).sort( { timestamp : -1 } )
When you use a $project aggregation stage it should typically be the last stage in
your pipeline, used to specify which fields to return to the client.
Using a $project stage at the beginning or middle of a pipeline to
reduce the number of fields passed to subsequent pipeline stages is
unlikely to improve performance, because the database performs this
optimization automatically.
For more information, see Project Fields to Return from Query.
Example
To achieve a covered query, index the projected fields. The ESR (Equality, Sort, Range) rule applies to the order of fields in the index.
For example, consider the following index on the inventory collection:
db.inventory.createIndex( { type: 1, _id: 1, price: 1, item: 1, expiryDate: 1} )
The above query, while technically correct, is not structured to optimize query performance.
The following query applies the ESR rule for a more efficient compound index:
db.inventory.aggregate([ { $match: {type: "food", expiryDate: { $gt: ISODate("2025-07-10T00:00:00Z") }}}, { $sort: { item: 1 }}, { $project: { _id: 1, price: 1} } ])
The index and query follow the ESR rule:
typeis used for an equality match (E), so it is the first field in the index.itemis used for sorting (S), so it is aftertypein the index.expiryDateis used for a range query (R), so it is the last field in the index.
Limit Query Results
MongoDB cursors return results in batches. If you know
how many results you need, pass that value to the limit()
method to reduce network resource usage.
Limit results after sorting so you know which documents are returned.
For example, the following query returns only the 10 most recent results from the
posts collection:
db.posts.find().sort( { timestamp : -1 } ).limit(10)
For more information, see limit().
Use Index Hints
The query optimizer
selects the optimal index for a specific operation. However,
you can force a specific index using the hint() method.
This is useful for performance testing or when a field appears in several indexes
and you need to guarantee which index MongoDB uses.
Use Server-Side Operations
Use the $inc operator to increment or decrement values in documents.
The operator increments the field value on the server side, as an alternative
to selecting a document, making simple modifications on the client side, and then
writing the document to the server. Additionaly, the operator prevents race conditions
when multiple application instances update the same field concurrently.
Run Covered Queries
A covered query is a query than can be satisfied entirely by an index without having to examine any documents. An index covers a query when all of the following are true:
All the fields in the query (including fields specified by the application and any fields needed internally, such as for sharding) are part of the index.
All the fields returned in the results are in the same index.
No fields in the query are equal to
null. For example, the following query predicates cannot result in covered queries:{ "field": null }{ "field": { $eq: null } }
Example
An inventory collection has the following index on the type and item fields:
db.inventory.createIndex( { type: 1, item: 1 } )
The index covers the following query, which filters on type and
item and returns only item:
db.inventory.find( { type: "food", item:/^c/ }, { item: 1, _id: 0 } )
For the specified index to cover the query, the projection document
must explicitly specify _id: 0 to exclude the _id field from
the result since the index does not include the _id field.
Embedded Documents
An index can cover a query on fields within embedded documents.
For example, consider the following userdata collection:
db.userdata.insertOne( { _id: 1, user: { login: "tester" } } )
The collection has the following index:
db.userdata.createIndex( { "user.login": 1 } )
The { "user.login": 1 } index covers the following query:
db.userdata.find( { "user.login": "tester" }, { "user.login": 1, _id: 0 } )
Note
To index fields in embedded documents, use dot notation. See Create an Index on an Embedded Field.
Multikey Covering
Multikey indexes can cover queries over non-array fields if the index tracks which field or fields make it multikey.
Multikey indexes cannot cover queries over array fields.
For an example, see Covered Queries on the multikey indexes page.
Performance
Covered queries match query conditions and return results using only the index. This is faster than fetching documents because index keys are typically smaller than documents and indexes are usually in RAM or stored sequentially on disk.
Limitations
Index Types
Not all index types support covered queries. See the documentation for the specific index type.
Sharded Collections
When run on mongos, indexes can only cover queries on
sharded collections if the index contains
the shard key.
Explain Results
To check whether a query is covered, use db.collection.explain()
or explain(). See Covered Queries.