一、基础概念
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,以保证查询的性能和可靠性。