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 rated field in the movies collection:

let ratingValue = <someUserInput>;
db.movies.find( { rated: ratingValue } );

To improve performance for this query, add an index to the movies collection on the rated field. [1] In mongosh, create indexes using the db.collection.createIndex() method:

db.movies.createIndex( { rated: 1 } )

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, if your query to the movies collection needs only the year, title, directors, and plot fields, specify those fields in the projection:

db.movies.find(
{},
{ year: 1, title: 1, directors: 1 }
).sort( { year: -1 } ).limit(3)

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 a movies collection:

db.movies.createIndex(
{ rated: 1, _id: 1, "imdb.rating": 1, title: 1, released: 1 }
)

The preceding index, while technically correct, isn't structured to optimize query performance.

The following query uses the ESR (Equality, Sort, Range) rule to structure a more efficient aggregation pipeline and improve query response times.

db.movies.aggregate( [
{ $match: { rated: "PG",
released: { $gt: ISODate("2000-01-01T00:00:00Z") } } },
{ $sort: { title: 1 } },
{ $limit: 5 },
{ $project: { _id: 1, "imdb.rating": 1 } }
] )

The index and query follow the ESR rule:

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

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

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

Sort results before applying a limit to ensure that the query returns the expected documents. For example, if you need only 10 results from your query to the movies collection, run the following query:

db.movies.find(
{},
{ title: 1, year: 1 }
).sort( { year: -1 } ).limit(10)

For more information, see limit().

The query optimizer typically selects the optimal index for a specific operation. However, you can force MongoDB to use a specific index using the hint() method. Use hint() to support performance testing or when you are querying a field that appears in several indexes to ensure that MongoDB uses the correct index.

Use the $inc operator to increment or decrement values in documents. The operator increments the value of the field on the server side, as an alternative to selecting a document, making changes in the client code, and then writing the entire document to the server. The $inc operator can also help avoid race conditions that occur when two application instances query for a document, manually increment a field, and save the entire document back at the same time.

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

A movies collection has the following index on the rated and title fields:

db.movies.createIndex( { rated: 1, title: 1 } )

The index covers the following operation which queries on the rated and title fields and returns only the title field:

db.movies.find(
{ rated: "PG", title: /^T/ },
{ title: 1, _id: 0 }
).limit(3)

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 doesn't include the _id field.

An index can cover a query on fields within embedded documents.

For example, consider the theaters collection from the sample_mflix dataset, which has documents with the following structure:

{
theaterId: <num>,
location: {
address: {
street1: "<address>",
city: "<city>",
state: "<state>",
zipcode: "<zip>"
},
geo: { ... }
}
}

The collection has the following index:

db.theaters.createIndex( { "location.address.city": 1 } )

The { "location.address.city": 1 } index covers the following query:

db.theaters.find(
{ "location.address.city": "Portland" },
{ "location.address.city": 1, _id: 0 }
).limit(1)

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