explain()是 MongoDB 最重要的性能分析工具,用于分析查询的执行计划和性能指标。explain() 的三种模式:模式 | 说明 | 使用场景 |
queryPlanner(默认) | 仅返回查询计划,不执行查询 | 快速查看优化器选择的计划 |
executionStats | 执行查询并返回统计信息 | 分析查询实际性能 |
allPlansExecution | 执行所有候选计划并返回信息 | 深入分析优化器决策 |
// 查询计划db.t_orders.find({ status: "paid" }).explain()// 执行统计db.t_orders.find({ status: "paid" }).explain("executionStats")// 所有计划执行信息db.t_orders.find({ status: "paid" }).explain("allPlansExecution")
{"queryPlanner": {"winningPlan": {"stage": "FETCH", // 执行阶段"inputStage": {"stage": "IXSCAN", // 使用索引扫描"keyPattern": { "status": 1 },"indexName": "idx_status","direction": "forward"}},"rejectedPlans": [...] // 被拒绝的计划},"executionStats": {"executionSuccess": true,"nReturned": 1000, // 返回文档数"executionTimeMillis": 15, // 执行时间(毫秒)"totalKeysExamined": 1000, // 扫描的索引条目数"totalDocsExamined": 1000, // 扫描的文档数"executionStages": {"stage": "FETCH","nReturned": 1000,"docsExamined": 1000,"inputStage": {"stage": "IXSCAN","nReturned": 1000,"keysExamined": 1000}}}}
Stage | 说明 | 是否理想 |
COLLSCAN | 全表扫描 | 需要创建索引 |
IXSCAN | 索引扫描 | 理想 |
FETCH | 根据索引回表获取文档 | 正常 |
SORT | 内存排序 | 需要为排序字段创建索引 |
SORT_KEY_GENERATOR | 生成排序键 | 配合 SORT 出现 |
PROJECTION_COVERED | 覆盖索引投影 | 理想(无需回表) |
PROJECTION_SIMPLE | 简单投影 | 正常 |
LIMIT | 限制返回数量 | 正常 |
SKIP | 跳过文档 | 正常(深度分页时低效) |
SHARD_MERGE | 分片结果合并 | 正常(分片集群) |
SINGLE_SHARD | 单分片查询 | 定向查询 |
指标 | 理想值 | 需优化 |
totalDocsExamined / nReturned | ≈ 1 | > 10(扫描过多文档) |
totalKeysExamined / nReturned | ≈ 1 | > 10(索引效率低) |
executionTimeMillis | < 100ms | > 1000ms(需优化) |
stage | IXSCAN | COLLSCAN(缺少索引) |
是否有 SORT 阶段 | 无 | 有(内存排序) |
// 查看当前慢查询阈值db.getProfilingStatus()// { "was": 1, "slowms": 100 }// 设置慢查询阈值为200msdb.setProfilingLevel(1, { slowms: 200 })// level 0: 关闭// level 1: 仅记录慢查询// level 2: 记录所有操作(慎用,性能影响大)
currentOp 能够实时捕获数据库当前正在执行的所有活动操作快照,适合排查当前性能问题。// 查看所有当前操作db.currentOp()// 查看执行时间超过5秒的操作db.currentOp({"active": true,"secs_running": { "$gt": 5 }})// 查看等待锁的操作db.currentOp({"waitingForLock": true})// 查看特定集合的操作db.currentOp({"ns": "mydb.t_orders"})// 终止执行时间超过10秒的操作db.currentOp().inprog.forEach(function(item){if(item.secs_running > 10 )db.killOp(item.opid)})
serverStatus 用于获取 MongoDB 实例全局状态快照与计数器信息。db.serverStatus()// 关注的关键指标{"connections": {"current": 50, // 当前连接数"available": 950, // 可用连接数"totalCreated": 1000},"opcounters": {"insert": 10000,"query": 50000,"update": 5000,"delete": 1000},"mem": {"resident": 4096, // 常驻内存(MB)"virtual": 8192 // 虚拟内存(MB)},"wiredTiger": {"cache": {"bytes currently in the cache": ...,"maximum bytes configured": ...}}}
// 1. 确认是否全表扫描db.t_orders.find({ status: "paid" }).explain("executionStats")// 检查 winningPlan.stage 是否为 COLLSCAN// 2. 检查现有索引db.t_orders.getIndexes()// 3. 创建合适的索引db.t_orders.createIndex({ status: 1 }, { background: true })// 4. 验证索引生效db.t_orders.find({ status: "paid" }).explain("executionStats")// 确认 stage 变为 IXSCAN
原因 | 解决方案 |
缺少索引 | 为查询条件字段创建索引 |
查询条件不匹配索引 | 调整查询或创建合适的复合索引 |
使用了无法利用索引的操作符 | 避免使用 $ne、$nin、无前缀正则等 |
索引选择性低 | 创建复合索引提高选择性 |
Sort operation used more than the maximum 33554432 bytes of RAM。// 1. 确认是否内存排序db.t_orders.find({ status: "paid" }).sort({ createTime: -1 }).explain("executionStats")// 检查是否有 SORT 阶段// 2. 创建支持排序的索引db.t_orders.createIndex({ status: 1, createTime: -1 }, { background: true })// 3. 验证索引排序db.t_orders.find({ status: "paid" }).sort({ createTime: -1 }).explain("executionStats")// 确认无 SORT 阶段
方案 | 说明 |
创建索引(推荐) | 为排序字段创建索引,索引排序无内存限制。 |
减少排序数据量 | 增加筛选条件,减少需要排序的文档数。 |
分页优化 | |
临时方案 | 使用 allowDiskUse(性能较差)。 |
// 1. 查看分片数据分布db.t_orders.getShardDistribution()// 2. 查看chunk分布db.getSiblingDB("config").chunks.aggregate([{ $match: { ns: "mydb.t_orders" } },{ $group: { _id: "$shard", count: { $sum: 1 } } }])// 3. 检查分片键选择db.getSiblingDB("config").collections.findOne({ _id: "mydb.t_orders" })
原因 | 解决方案 |
分片键是单调递增字段 | 使用 Hash 分片或复合分片键 |
分片键基数太低 | 选择高基数字段或复合键 |
写入集中在最新数据 | 使用时间戳 Hash 分片 |
某些热点值访问频繁 | 业务层缓存热点数据 |
connection pool exhausted。// 1. 查看当前连接数db.serverStatus().connections// { "current": 950, "available": 50, "totalCreated": 10000 }// 2. 查看连接来源db.currentOp(true).inprog.forEach(function(op) {if (op.client) print(op.client);})// 3. 检查应用连接池配置// 确认maxPoolSize设置是否合理
方案 | 说明 |
优化连接池 | 减少单应用的 maxPoolSize |
检查连接泄漏 | 确保连接正确释放 |
增加实例规格 | 提升实例支持的最大连接数 |
减少应用实例 | 降低总连接需求 |
// 1. 查看等待锁的操作db.currentOp({ "waitingForLock": true })// 2. 查看持有锁时间长的操作db.currentOp({"active": true,"secs_running": { "$gt": 10 }})// 3. 查看锁状态db.serverStatus().locks
原因 | 解决方案 |
长事务 | 缩短事务执行时间,拆分大事务 |
长时间运行的查询 | 优化查询,添加索引 |
前台建索引 | 使用 background:true |
DDL 操作 | 安排在低峰期执行 |
检查项 | 验证方法 | 通过标准 |
查询使用索引 | explain("executionStats") | stage 为 IXSCAN |
无内存排序 | explain("executionStats") | 无 SORT 阶段 |
扫描效率 | totalDocsExamined/nReturned | 比值接近1 |
返回字段最小化 | 检查是否使用投影 | 仅返回必要字段 |
分页方式合理 | 检查分页实现 | 使用基于排序字段的游标分页 |
检查项 | 验证方法 | 通过标准 |
Update 带条件 | 代码审查 | 禁止空条件更新 |
使用$set 更新 | 代码审查 | 禁止全文档替换 |
批量操作有控制 | 代码审查 | 单批次≤1000条 |
Write Concern 合理 | 代码审查 | 核心业务使用 majority |
// 列出所有索引db.t_orders.getIndexes()// 检查索引使用统计db.t_orders.aggregate([{ $indexStats: {} }])// 针对主要查询验证索引覆盖const 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("---");});
文档反馈