Skip to content

MongoDB 执行计划分析

执行计划基本概念

1. 查询阶段

MongoDB 查询执行计划包含多个阶段,每个阶段负责不同的查询操作:

阶段描述
COLLSCAN全集合扫描,遍历集合中的所有文档
IXSCAN索引扫描,使用索引查找文档
FETCH从集合中获取文档
SORT排序操作
PROJECTION投影操作,筛选返回的字段
AGGREGATE聚合操作
LIMIT限制返回结果数量
SKIP跳过指定数量的文档

2. 执行统计信息

执行计划包含以下关键统计信息:

  • executionTimeMillis:查询执行时间,单位毫秒
  • totalKeysExamined:检查的索引键数量
  • totalDocsExamined:检查的文档数量
  • nReturned:返回的文档数量
  • winningPlan:MongoDB 选择的最优执行计划
  • rejectedPlans:MongoDB 考虑但未选择的执行计划

使用 explain() 方法分析执行计划

1. explain() 方法的使用

基本语法

javascript
db.collection.find({ query }).explain([verbose])

verbose 参数选项

选项描述
"queryPlanner"只返回查询计划信息(默认)
"executionStats"返回查询计划和执行统计信息
"allPlansExecution"返回查询计划、执行统计信息和所有考虑的计划

2. 示例

基本查询分析

javascript
// 连接到 MongoDB
mongosh

// 使用 explain() 分析查询计划
use mydb
db.mycollection.find({ name: "test" }).explain("executionStats")

索引查询分析

javascript
// 创建索引
db.mycollection.createIndex({ name: 1 })

// 分析索引查询计划
db.mycollection.find({ name: "test" }).explain("executionStats")

复杂查询分析

javascript
// 分析复杂查询计划
db.mycollection.find({ name: "test", age: { $gt: 30 } }).sort({ createdAt: -1 }).limit(10).explain("executionStats")

执行计划输出解读

1. queryPlanner 部分

json
{
  "queryPlanner": {
    "plannerVersion": 1,
    "namespace": "mydb.mycollection",
    "indexFilterSet": false,
    "parsedQuery": {
      "name": {
        "$eq": "test"
      }
    },
    "winningPlan": {
      "stage": "FETCH",
      "inputStage": {
        "stage": "IXSCAN",
        "keyPattern": {
          "name": 1
        },
        "indexName": "name_1",
        "isMultiKey": false,
        "multiKeyPaths": {
          "name": []
        },
        "isUnique": false,
        "isSparse": false,
        "isPartial": false,
        "indexVersion": 2,
        "direction": "forward",
        "indexBounds": {
          "name": [
            "["test", "test"]"
          ]
        }
      }
    },
    "rejectedPlans": []
  }
}

2. executionStats 部分

json
{
  "executionStats": {
    "executionSuccess": true,
    "nReturned": 1,
    "executionTimeMillis": 10,
    "totalKeysExamined": 1,
    "totalDocsExamined": 1,
    "executionStages": {
      "stage": "FETCH",
      "nReturned": 1,
      "executionTimeMillisEstimate": 5,
      "works": 2,
      "advanced": 1,
      "needTime": 0,
      "needYield": 0,
      "saveState": 0,
      "restoreState": 0,
      "isEOF": 1,
      "docsExamined": 1,
      "alreadyHasObj": 0,
      "inputStage": {
        "stage": "IXSCAN",
        "nReturned": 1,
        "executionTimeMillisEstimate": 2,
        "works": 2,
        "advanced": 1,
        "needTime": 0,
        "needYield": 0,
        "saveState": 0,
        "restoreState": 0,
        "isEOF": 1,
        "keyPattern": {
          "name": 1
        },
        "indexName": "name_1",
        "isMultiKey": false,
        "multiKeyPaths": {
          "name": []
        },
        "isUnique": false,
        "isSparse": false,
        "isPartial": false,
        "indexVersion": 2,
        "direction": "forward",
        "indexBounds": {
          "name": [
            "["test", "test"]"
          ]
        },
        "keysExamined": 1,
        "seeks": 1,
        "dupsTested": 0,
        "dupsDropped": 0,
        "seenInvalidated": 0,
        "matchTested": 0
      }
    }
  }
}

常见执行计划问题及优化

1. 全集合扫描(COLLSCAN)

症状

  • 执行计划中出现 "stage": "COLLSCAN"
  • totalDocsExamined 远大于 nReturned
  • 执行时间长

解决方案

  • 创建合适的索引,避免全集合扫描
  • 优化查询条件,确保使用索引
  • 考虑使用覆盖索引,减少回表查询

2. 索引扫描但扫描文档过多

症状

  • 执行计划中出现 "stage": "IXSCAN"
  • totalKeysExamined 远大于 nReturned
  • 执行时间长

解决方案

  • 优化索引设计,创建更精确的索引
  • 调整查询条件,确保使用最匹配的索引
  • 使用 hint() 指定索引

3. 排序操作(SORT)

症状

  • 执行计划中出现 "stage": "SORT"
  • 执行时间长

解决方案

  • 创建包含排序字段的复合索引
  • 限制排序结果集大小
  • 考虑在应用层进行排序

4. 跳过大量文档(SKIP)

症状

  • 执行计划中出现 "stage": "SKIP"
  • skip 参数值较大
  • 执行时间长

解决方案

  • 使用范围查询替代 skip(),例如使用 { _id: { $gt: ObjectId(...) } }
  • 考虑使用游标分页
  • 限制分页深度

执行计划分析工具

1. MongoDB Compass

使用方法

  1. 打开 MongoDB Compass
  2. 连接到 MongoDB 实例
  3. 导航到 "Collections" 标签页
  4. 选择集合,点击 "Filter" 按钮
  5. 输入查询条件,点击 "Explain Plan" 按钮
  6. 查看执行计划和统计信息

2. mongosh

使用方法

  1. 连接到 MongoDB
  2. 使用 explain() 方法分析查询计划
  3. 查看输出结果,分析执行计划

3. 第三方工具

  • MongoDB Atlas:提供可视化的执行计划分析工具
  • Robo 3T:支持执行计划分析
  • Studio 3T:提供高级的执行计划分析功能

执行计划分析最佳实践

1. 定期分析执行计划

  • 定期分析慢查询的执行计划
  • 监控查询性能,及时发现性能问题
  • 新查询上线前分析执行计划

2. 关注关键指标

  • totalDocsExamined / nReturned:比率越低越好
  • executionTimeMillis:执行时间越短越好
  • winningPlan:优先选择使用索引的执行计划

3. 优化索引设计

  • 根据查询模式创建合适的索引
  • 避免过多索引,每个索引都会增加写操作开销
  • 使用覆盖索引,减少回表查询
  • 定期重建索引,提高索引性能

4. 优化查询语句

  • 限制结果集大小,使用 limit()skip()
  • 使用投影,只返回需要的字段
  • 避免全集合扫描,使用索引
  • 优化排序操作,使用索引排序

常见问题(FAQ)

Q1: 如何确定查询是否使用了索引?

A1: 可以通过以下方法确定查询是否使用了索引:

  • 使用 explain("executionStats") 分析执行计划
  • 检查执行计划中的 stage 字段,若为 IXSCAN 则使用了索引,若为 COLLSCAN 则未使用索引
  • 查看 totalKeysExaminedtotalDocsExamined 字段,若 totalKeysExamined 大于 0 则使用了索引

Q2: 如何选择合适的索引?

A2: 选择合适索引的方法:

  • 分析查询模式,确定查询中使用的字段
  • 优先为频繁查询的字段创建索引
  • 考虑查询的排序和分组字段
  • 避免创建过多索引,每个索引都会增加写操作开销
  • 使用 explain() 分析索引效果

Q3: 如何优化慢查询?

A3: 优化慢查询的方法:

  • 分析执行计划,识别性能瓶颈
  • 优化索引设计,创建合适的索引
  • 优化查询语句,避免全集合扫描
  • 限制结果集大小,使用 limit()skip()
  • 使用投影,只返回需要的字段

Q4: 如何处理复杂查询?

A4: 处理复杂查询的方法:

  • 分解复杂查询为多个简单查询
  • 使用聚合管道优化复杂查询
  • 考虑使用 $lookup 替代多次查询
  • 优化索引设计,支持复杂查询

Q5: 如何验证索引的有效性?

A5: 验证索引有效性的方法:

  • 使用 explain() 分析查询计划,检查是否使用了索引
  • 监控查询执行时间,查看索引是否提高了查询性能
  • 查看索引使用率,定期清理未使用的索引
  • 测试不同索引的效果,选择最优索引

Q6: 如何分析聚合查询的执行计划?

A6: 分析聚合查询执行计划的方法:

  • 使用 db.collection.aggregate(pipeline).explain("executionStats") 分析聚合查询计划
  • 查看聚合管道的每个阶段的执行情况
  • 优化聚合管道,使用 $match 尽早过滤数据
  • 优化聚合操作,使用索引支持聚合查询

示例:优化查询执行计划

问题描述

查询 db.users.find({ age: { $gt: 30 } }).sort({ createdAt: -1 }) 执行时间过长,需要优化。

分析执行计划

javascript
// 分析执行计划
db.users.find({ age: { $gt: 30 } }).sort({ createdAt: -1 }).explain("executionStats")

执行计划输出

json
{
  "executionStats": {
    "executionSuccess": true,
    "nReturned": 100,
    "executionTimeMillis": 500,
    "totalKeysExamined": 0,
    "totalDocsExamined": 100000,
    "executionStages": {
      "stage": "SORT",
      "nReturned": 100,
      "executionTimeMillisEstimate": 450,
      "works": 100002,
      "advanced": 100,
      "needTime": 99901,
      "needYield": 0,
      "saveState": 781,
      "restoreState": 781,
      "isEOF": 1,
      "sortPattern": {
        "createdAt": -1
      },
      "memUsage": 12582912,
      "memLimit": 33554432,
      "inputStage": {
        "stage": "COLLSCAN",
        "filter": {
          "age": {
            "$gt": 30
          }
        },
        "nReturned": 50000,
        "executionTimeMillisEstimate": 100,
        "works": 100001,
        "advanced": 50000,
        "needTime": 50000,
        "needYield": 0,
        "saveState": 781,
        "restoreState": 781,
        "isEOF": 1,
        "docsExamined": 100000,
        "executionTimeMillisEstimate": 100
      }
    }
  }
}

分析问题

  • 执行计划显示 COLLSCAN(全集合扫描)
  • totalDocsExamined 为 100000,nReturned 为 100,比率过高
  • executionTimeMillis 为 500ms,执行时间过长
  • 存在 SORT 阶段,消耗大量内存

优化方案

创建复合索引

javascript
// 创建复合索引,包含查询字段和排序字段
db.users.createIndex({ age: 1, createdAt: -1 })

重新分析执行计划

javascript
db.users.find({ age: { $gt: 30 } }).sort({ createdAt: -1 }).explain("executionStats")

优化后的执行计划

json
{
  "executionStats": {
    "executionSuccess": true,
    "nReturned": 100,
    "executionTimeMillis": 10,
    "totalKeysExamined": 100,
    "totalDocsExamined": 100,
    "executionStages": {
      "stage": "FETCH",
      "nReturned": 100,
      "executionTimeMillisEstimate": 5,
      "works": 101,
      "advanced": 100,
      "needTime": 0,
      "needYield": 0,
      "saveState": 0,
      "restoreState": 0,
      "isEOF": 1,
      "docsExamined": 100,
      "inputStage": {
        "stage": "IXSCAN",
        "nReturned": 100,
        "executionTimeMillisEstimate": 2,
        "works": 101,
        "advanced": 100,
        "needTime": 0,
        "needYield": 0,
        "saveState": 0,
        "restoreState": 0,
        "isEOF": 1,
        "keyPattern": {
          "age": 1,
          "createdAt": -1
        },
        "indexName": "age_1_createdAt_-1",
        "isMultiKey": false,
        "direction": "forward",
        "indexBounds": {
          "age": [
            "(30.0, inf.0]"
          ],
          "createdAt": [
            "[MaxKey, MinKey]"
          ]
        },
        "keysExamined": 100,
        "seeks": 1
      }
    }
  }
}

优化效果

  • 执行计划显示 IXSCAN(索引扫描)
  • totalDocsExamined 为 100,nReturned 为 100,比率优化
  • executionTimeMillis 为 10ms,执行时间大幅减少
  • 不再有 SORT 阶段,使用索引排序