您的位置:

Mongo Explain详解

一、基础概念

Mongo Explain是MongoDB提供的一个查询优化工具,可以帮助开发者分析查询性能并作出优化建议。

Explain可以告诉我们查询使用了哪些索引,以及在查询过程中使用了哪些操作,如sort、limit、skip等等。Explain同样可以分析聚合(aggregation)和地理位置查询(geospatial)的性能。

Explain的语法非常简单,只需要在查询语句前加上"explain()"即可:

db.collection.find().explain()

二、执行计划

执行计划说明了MongoDB是如何执行查询的。执行计划是一个树状结构,其中根节点是查询的最终结果,叶子节点是执行查询使用到的索引。

以下是一个典型的执行计划示例:

{
  "queryPlanner": {
    "plannerVersion": 1,
    "namespace": "test.orders",
    "indexFilterSet": false,
    "parsedQuery": {
      "orderDate": {
        "$gt": ISODate("2020-01-01T00:00:00Z")
      }
    },
    "winningPlan": {
      "stage": "COLLSCAN",
      "filter": {
        "orderDate": {
          "$gt": ISODate("2020-01-01T00:00:00Z")
        }
      },
      "direction": "forward"
    },
    "rejectedPlans": []
  },
  "serverInfo": {...},
  "ok": 1
}

在这个执行计划中,有以下几个关键信息:

  • plannerVersion:MongoDB查询计划的版本信息。
  • namespace:查询的命名空间,即查询的集合名称。
  • parsedQuery:解析后的查询条件。
  • winningPlan:MongoDB认为最优的查询方案,所有叶节点都是基于这个方案执行的。
  • rejectedPlans:其他可行的查询方案,MongoDB认为不如winningPlan优秀。

三、优化建议

Explain不仅可以告诉我们查询有哪些问题,还可以提供一些针对性的优化建议。

以下是一个典型的优化建议结果:

{
  "queryPlanner": {...},
  "serverInfo": {...},
  "ok": 1,
  "queryHash": "....",
  "planCacheKey": "....",
  "serverPipelineEstimator": {...},
  "ok": 1,
  "rejectedPlans": [],
  "provenance": {...},
  "executionStats": {
    "executionSuccess": true,
    "nReturned": 23,
    "executionTimeMillis": 12,
    "totalKeysExamined": 1250,
    "totalDocsExamined": 1250,
    "executionStages": {...},
    "allPlansExecution": []
  },
  "serverInfo": {...},
  "ok": 1
}

在这个优化建议结果中,有以下几个关键信息:

  • planCacheKey:MongoDB为查询方案生成的一个缓存键值。
  • serverPipelineEstimator:MongoDB使用查询记录的信息计算出的查询性能的平均值。
  • executionStats:查询的统计信息,包括执行时间、返回结果数量、扫描的索引数量等等。
  • allPlansExecution:Explain会为所有可行的查询方案生成执行统计信息,并将其存储在这个属性中。如果有多个查询方案,我们可以将它们的执行统计信息进行比较,找出性能最优的方案。

四、使用案例

1. 查询优化

我们可以使用explain()分析查询语句,并作出优化建议。

代码示例:

db.orders.find({ orderDate: { $gt: ISODate("2020-01-01T00:00:00Z") } }).explain()

运行结果如下:

{
  "queryPlanner": {
    ...
    "winningPlan" : {
      "stage" : "COLLSCAN",
      "filter" : {
        "orderDate" : {
          "$gt" : ISODate("2020-01-01T00:00:00Z")
         }
      },
      "direction" : "forward"
     },
   "rejectedPlans" : [ ]
  },
  "serverInfo": {...}
}

在这个结果中,可以看到我们的查询方案仅仅是一个COLLSCAN,即全表扫描。这说明需要为这个查询增加索引,以提高查询性能。

2. 比较查询方案

Explain可以为多个查询方案生成执行统计信息,我们可以使用allPlansExecution属性比较它们的性能。

代码示例:

db.orders.explain("executionStats").aggregate([
  { $match: { orderDate: { $gt: ISODate("2020-01-01T00:00:00Z") } } },
  { $group: { _id: "$status", total: { $sum: "$amount" } } }
])

这个查询包括两个阶段:匹配和分组。我们可以使用Explain来比较以下两种方案:

方案1:使用索引进行匹配

db.orders.explain("executionStats").aggregate([
  { $match: { orderDate: { $gt: ISODate("2020-01-01T00:00:00Z") } } },
  { $group: { _id: "$status", total: { $sum: "$amount" } } }
], { hint: "orderDate_1" })

方案2:使用投影顺序的索引

db.orders.explain("executionStats").aggregate([
  { $group: { _id: "$status", total: { $sum: "$amount" } } },
  { $match: { orderDate: { $gt: ISODate("2020-01-01T00:00:00Z") } } }
], { hint: "status_1_orderDate_1" })

我们可以看到,方案1使用的是orderDate_1索引,方案2使用的是status_1_orderDate_1索引。这两个方案的执行结果可以使用allPlansExecution属性进行比较。

运行结果如下:

{
  "queryPlanner": {...},
  "serverPipelineEstimator": {...},
  "ok" : 1,
  "serverInfo": {...},
  "provenance": {...},
  "executionStats": {
    "executionSuccess": true,
    "nReturned": 5,
    "executionTimeMillis": 193,
    "totalKeysExamined": 1000000,
    "totalDocsExamined": 1000000,
    "executionStages": {...},
    "allPlansExecution": [
      {
        "nReturned": 5,
        "executionTimeMillis": 223,
        "totalKeysExamined": 1250,
        "totalDocsExamined": 1250,
        "executionStages": {...},
        "indexName": "orderDate_1",
        "rejectedByExpiration": false
      },
      {
        "nReturned": 5,
        "executionTimeMillis": 184,
        "totalKeysExamined": 1000000,
        "totalDocsExamined": 1000000,
        "executionStages": {...},
        "indexName": "status_1_orderDate_1",
        "rejectedByExpiration" : false
      }
    ]
  },
  "ok": 1
}

可以看到方案1和方案2的性能都不错,其中方案1使用的索引key数量较少,但是总共扫描了1250条记录;方案2使用的索引key数量更多,但是总共扫描了1000000条记录。综合考虑,方案2更适合这个查询。

五、总结

Explain是MongoDB中非常有用的查询优化工具,它可以帮助我们分析查询性能、找到性能瓶颈、作出优化建议等等。在项目开发过程中,我们应该积极使用Explain,以保证查询的性能和可靠性。