How to use explain() to diagnose slow MongoDB queries
explain verbosity levels, winning plan vs rejected plans, executionStats fields, SORT stage warning, index hint, slow query log
Why explain() is essential
MongoDB's explain() method reveals exactly how the query planner executed a query โ which index was used, how many documents were scanned, and how long execution took. Run it before and after adding any index to measure the actual impact. Use 'executionStats' mode for the most actionable data without the noise of all plan candidates.
const plan = await db.collection('orders')
.find({ userId: 'u1', status: 'pending' })
.explain('executionStats')
const stats = plan.executionStats
console.log('Stage: ', plan.queryPlanner.winningPlan.stage)
console.log('Docs scanned: ', stats.totalDocsExamined)
console.log('Docs returned: ', stats.nReturned)
console.log('Time (ms): ', stats.executionTimeMillis)
// Healthy output:
// Stage: IXSCAN Docs scanned: 5 Docs returned: 5 Time: 1
// Unhealthy:
// Stage: COLLSCAN Docs scanned: 500000 Docs returned: 5 Time: 340Red flags in the output
If totalDocsExamined is much larger than nReturned, a selective index is missing. A SORT stage appearing above an IXSCAN in the winning plan means MongoDB performed an in-memory sort after scanning โ the index does not cover the requested sort order, which is expensive at scale.
// Force a specific index during investigation (remove before production)
db.collection('orders')
.find({ status: 'pending' })
.hint({ status: 1, createdAt: -1 })
.explain('executionStats')The most actionable habit when working with any new query pattern is to call explain('executionStats') before the query goes to production. Five minutes reading the plan output can prevent a slow query that only surfaces under production data volume, where a COLLSCAN that took 2 ms in development takes 8 seconds against 50 million real documents.
