explain() is the most important performance analysis tool in MongoDB, used to analyze the execution plan and performance metrics of queries. The three modes of explain():Mode | Description | Application Scenario |
queryPlanner (default) | Returns only the query plan, without executing the query. | Quickly view the plan selected by the optimizer. |
executionStats | Executes the query and returns statistics. | Analyze query actual performance. |
allPlansExecution | Executes all candidate plans and returns information. | In-depth analysis of optimizer decisions. |
// Query plandb.t_orders.find({ status: "paid" }).explain()// Execute statisticsdb.t_orders.find({ status: "paid" }).explain("executionStats")// All plan execution informationdb.t_orders.find({ status: "paid" }).explain("allPlansExecution")
{"queryPlanner": {"winningPlan": {"stage": "FETCH", // Execution stage"inputStage": {"stage": "IXSCAN", // Uses index scan"keyPattern": { "status": 1 },"indexName": "idx_status","direction": "forward"}},"rejectedPlans": [...] // Rejected plans},"executionStats": {"executionSuccess": true,"nReturned": 1000, // Number of documents returned"executionTimeMillis": 15, // Execution time (milliseconds)"totalKeysExamined": 1000, // Number of index entries scanned"totalDocsExamined": 1000, // Number of documents scanned"executionStages": {"stage": "FETCH","nReturned": 1000,"docsExamined": 1000,"inputStage": {"stage": "IXSCAN","nReturned": 1000,"keysExamined": 1000}}}}
Stage | Description | Ideal or Not |
COLLSCAN | Full Table Scan | Index creation required |
IXSCAN | Index scanning | Ideal |
FETCH | Fetch documents by returning to the table via index. | Normal |
SORT | In-memory sorting | Create an index for the sort field. |
SORT_KEY_GENERATOR | Generate sort keys. | Appears in conjunction with SORT |
PROJECTION_COVERED | Covered index projection | Ideal (no table lookup required) |
PROJECTION_SIMPLE | Simple projection | Normal |
LIMIT | Limit the number of returned results. | Normal |
SKIP | Skip documents. | Normal (inefficient during deep pagination) |
SHARD_MERGE | Shard result merging | Normal (in a sharded cluster) |
SINGLE_SHARD | Single-shard query | Targeted query |
Metric | Ideal Value | Requires Optimization |
totalDocsExamined / nReturned | ≈ 1 | > 10 (excessive document scanning) |
totalKeysExamined / nReturned | ≈ 1 | > 10 (low index efficiency) |
executionTimeMillis | < 100ms | > 1000ms (requires optimization) |
stage | IXSCAN | COLLSCAN (missing index) |
SORT Stage or Not | None. | Yes (in-memory sorting) |
// View the current slow log query thresholddb.getProfilingStatus()// { "was": 1, "slowms": 100 }// Set the slow log query threshold to 200 msdb.setProfilingLevel(1, { slowms: 200 })// level 0: Disable// level 1: Log only slow log queries// level 2: Log all operations (Use with caution, as it has a significant performance impact)
currentOp can capture a real-time snapshot of all active operations currently being executed in the database, making it suitable for troubleshooting current performance issues.// View all current operationsdb.currentOp()// View operations whose execution time exceeds 5 secondsdb.currentOp({"active": true,"secs_running": { "$gt": 5 }})// View operations that are waiting for locksdb.currentOp({"waitingForLock": true})// View operations for a specific collectiondb.currentOp({"ns": "mydb.t_orders"})// Terminate operations whose execution time exceeds 10 secondsdb.currentOp().inprog.forEach(function(item){if(item.secs_running > 10 )db.killOp(item.opid)})
serverStatus is used to obtain a global status snapshot and counter information for a MongoDB instance.db.serverStatus()// Key metrics to focus on{"connections": {"current": 50, // current number of connections"available": 950, // number of available connections"totalCreated": 1000},"opcounters": {"insert": 10000,"query": 50000,"update": 5000,"delete": 1000},"mem": {"resident": 4096, // resident memory (MB)"virtual": 8192 // virtual memory (MB)},"wiredTiger": {"cache": {"bytes currently in the cache": ...,"maximum bytes configured": ...}}}
// 1. Confirm whether a full table scan is performeddb.t_orders.find({ status: "paid" }).explain("executionStats")// Check whether winningPlan.stage is COLLSCAN// 2. Check existing indexesdb.t_orders.getIndexes()// 3. Create appropriate indexesdb.t_orders.createIndex({ status: 1 }, { background: true })// 4. Verify that the index takes effectdb.t_orders.find({ status: "paid" }).explain("executionStats")// Confirm that the stage has changed to IXSCAN
Cause | Solution |
Missing index | Create an index for the query condition field. |
Query conditions do not match the index. | Adjust the query or create a suitable composite index. |
Using operators that cannot leverage indexes. | Avoid using $ne, $nin, prefixless regular expressions, and similar operators. |
Low index selectivity | Create a composite index to improve selectivity. |
Sort operation used more than the maximum 33554432 bytes of RAM.// 1. Confirm whether an in-memory sort is performeddb.t_orders.find({ status: "paid" }).sort({ createTime: -1 }).explain("executionStats")// Check whether a SORT stage exists// 2. Create an index that supports sortingdb.t_orders.createIndex({ status: 1, createTime: -1 }, { background: true })// 3. Verify index sortingdb.t_orders.find({ status: "paid" }).sort({ createTime: -1 }).explain("executionStats")// Confirm that no SORT stage exists
Solution | Description |
Create index (recommended) | Create an index for the sort field. Index sorting has no memory limit. |
Reduce the amount of data to be sorted | Add filter conditions to reduce the number of documents that need to be sorted. |
Pagination optimization | Use cursor-based pagination on the sort field to replace deep pagination (skip+sort). For details, see Avoid Deep Pagination. |
Temporary solution | Use allowDiskUse (poor performance). |
// 1. View the shard data distributiondb.t_orders.getShardDistribution()// 2. View the chunk distributiondb.getSiblingDB("config").chunks.aggregate([{ $match: { ns: "mydb.t_orders" } },{ $group: { _id: "$shard", count: { $sum: 1 } } }])// 3. Check the shard key selectiondb.getSiblingDB("config").collections.findOne({ _id: "mydb.t_orders" })
Cause | Solution |
The shard key is a monotonically increasing field. | Use Hash sharding or composite shard keys. |
Low shard key cardinality | Select high-cardinality fields or composite keys. |
Write operations concentrated on the latest data. | Use timestamp-based Hash sharding. |
Frequent access to certain hot values. | Cache hot data at the business layer. |
connection pool exhausted.// 1. View the current number of connectionsdb.serverStatus().connections// { "current": 950, "available": 50, "totalCreated": 10000 }// 2. View the connection sourcesdb.currentOp(true).inprog.forEach(function(op) {if (op.client) print(op.client);})// 3. Check the application connection pool configuration// Confirm whether the maxPoolSize setting is appropriate
Solution | Description |
Optimize connection pool. | Reduce the maxPoolSize for a single application. |
Check for connection leaks. | Ensure that connections are released properly. |
Increase instance specification. | Increase the maximum number of connections supported by an instance. |
Reduce application instances. | Reduce the total connection demand. |
// 1. View operations in lock waitingdb.currentOp({ "waitingForLock": true })// 2. View operations that hold locks for a long timedb.currentOp({"active": true,"secs_running": { "$gt": 10 }})// 3. View the lock statusdb.serverStatus().locks
Cause | Solution |
Long transactions | Shorten transaction execution time and split large transactions. |
Long-running queries | Optimize queries and add indexes. |
Creating indexes in the foreground | Use background:true. |
DDL operations | Schedule execution during off-peak hours. |
Check Item | Verification Method | Passing Criteria |
Query uses indexes | explain("executionStats") | The stage is IXSCAN. |
No in-memory sorting | explain("executionStats") | No SORT stage |
Scan efficiency | totalDocsExamined/nReturned | The ratio is close to 1. |
Minimize returned fields. | Check whether projection is used. | Only necessary fields are returned. |
Reasonable pagination method | Check pagination implementation. | Cursor-based pagination on the sort field |
Check Item | Verification Method | Passing Criteria |
Conditional Update | Code review | Prohibit Update Without Condition. |
Update Using $set | Code review | Prohibit Full Document Replacement. |
Batch Operations with Controls | Code review | Batch Size ≤ 1000 Items |
Write Concern appropriateness or not | Code review | Use `majority` for core business. |
// List all indexesdb.t_orders.getIndexes()// Check index usage statisticsdb.t_orders.aggregate([{ $indexStats: {} }])// Verify index coverage for the primary queriesconst mainQueries = [{ status: "paid" },{ customerId: "C001", createTime: { $gte: ISODate("2024-01-01") } },{ orderId: "ORD001" }];mainQueries.forEach(query => {const plan = db.t_orders.find(query).explain("executionStats");print("Query:", JSON.stringify(query));print("Stage:", plan.queryPlanner.winningPlan.stage);print("DocsExamined:", plan.executionStats.totalDocsExamined);print("---");});
피드백