Skip to content

Query Performance

Understanding Query Plans

Winning plan, rejected plans, and what executionStats adds on top of queryPlanner.

AdvancedAbout 6 minutes

A plan is a tree of stages. You read it inside-out: the leaf does the scan, parents FETCH, SORT, LIMIT, PROJECTION. winningPlan is what ran. rejectedPlans are what lost the race — useful when the wrong index won.

  1. 01IXSCAN (city_1)
  2. 02FETCH (load documents)
  3. 03SORT (if the index did not provide order)
  4. 04LIMIT / PROJECTION
db.products.createIndex({ inStock: 1, price: 1 })
db.products.find(
  { inStock: true },
  { _id: 0, inStock: 1, price: 1 }
).sort({ price: 1 }).explain("executionStats")
// IXSCAN only — no FETCH. totalDocsExamined: 0
Covered query — every filter and projected field is in the index, _id excluded

A covered query never loads the document. _id is in every document and in the _id index — if you project _id (the default) the query is not covered unless _id is also in your compound index. { _id: 0 } is part of covering. Do not add five extra keys to cover a weekly report.

queryPlanner tells you which plan. executionStats tells you what it cost. allPlansExecution shows trial stats for the competitors — use it when two indexes look plausible and the wrong one wins. A SORT stage above FETCH means the index did not satisfy .sort() (ESR). LIMIT pushed into the index is why $sort+$limit is cheap.

Interview question

What is a covered query?

Think about it first.

Practice

Filter, sort, project — the plan you want is IXSCAN, not SORT.