Learn the "why" behind slow queries and how to fix them in our 2-Part Webinar.
Register now >
Docs Menu
Docs Home
/ /

Query Optimization

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.

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.

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 _id is 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.

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.

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:

  • type is used for an equality match (E), so it is the first field in the index.

  • item is used for sorting (S), so it is after type in the index.

  • expiryDate is used for a range query (R), so it is the last field in the index.

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().

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 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.

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 } }

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.

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 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.

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.

Not all index types support covered queries. See the documentation for the specific index type.

When run on mongos, indexes can only cover queries on sharded collections if the index contains the shard key.

To check whether a query is covered, use db.collection.explain() or explain(). See Covered Queries.

Back

Causal Consistency and Read and Write Concerns

Earn a Skill Badge

Master "Query Optimization" for free!

Learn more

On this page