做为近年最为火热的文档型数据库,MongoDB受到了愈来愈多人的关注,可是因为国内的MongoDB相关技术分享屈指可数,很多朋友向我抱怨无从下手。sql
《MongoDB干货系列》将从实际应用的角度来进行MongoDB的一些列干货的分享,将覆盖调优,troubleshooting等方面,但愿能对你们带来帮助。mongodb
若是但愿了解更多MongoDB基础的信息,还请你们Google下。数据库
要保证数据库处于高效、稳定的状态,除了良好的硬件基础、高效高可用的数据库架构、贴合业务的数据模型以外,高效的查询语句也是不可少的。那么,如何查看并判断咱们的执行计划呢?咱们今天就来谈论下MongoDB的执行计划分析。服务器
MongoDB 3.0以后,explain的返回与使用方法与以前版本有了很多变化,介于3.0以后的优秀特点,本文仅针对MongoDB 3.0+的explain进行讨论。架构
现版本explain有三种模式,分别以下:性能
queryPlanner测试
executionStats优化
allPlansExecutionspa
因为文章字数缘由,本系列将分为三个部分。
第一部分
第二部分
第三部分翻译
本文是MongoDB执行计划分析详解的最后一个部分,咱们将对该如何分析exlain信息进行详细解读,并将针对实例进行explain分析详解。
首先,最为直观explain返回值是executionTimeMillis值,指的是咱们这条语句的执行时间,这个值固然是但愿越少越好。
且executionTimeMillis 与stage有一样的层数,即:
"executionStats" : { "executionSuccess" : true, "nReturned" : 29861, "executionTimeMillis" : 66948, "totalKeysExamined" : 29861, "totalDocsExamined" : 29861, "executionStages" : { "stage" : "FETCH", "nReturned" : 29861, "executionTimeMillisEstimate" : 66244, "works" : 29862, "advanced" : 29861, "needTime" : 0, "needFetch" : 0, "saveState" : 2934, "restoreState" : 2934, "isEOF" : 1, "invalidates" : 0, "docsExamined" : 29861, "alreadyHasObj" : 0, "inputStage" : { "stage" : "IXSCAN", "nReturned" : 29861, "executionTimeMillisEstimate" : 290, "works" : 29862, "advanced" : 29861, "needTime" : 0, "needFetch" : 0, "saveState" : 2934, "restoreState" : 2934, ...
其中有3个executionTimeMillis,分别是
executionStats.executionTimeMillis
该query的总体查询时间
executionStats.executionStages.executionTimeMillis
该查询根据index去检索document获取29861条具体数据的时间
executionStats.executionStages.inputStage.executionTimeMillis
该查询扫描29861行index所用时间
这三个值咱们都但愿越少越好,那么是什么影响这这三个返回值呢?
抛开硬件因素等不谈,咱们来进行下一层的剥离。
这里主要谈3个返回项,nReturned,totalKeysExamined与totalDocsExamined,分别表明该条查询返回的条目、索引扫描条目和文档扫描条目。
很好理解,这些都直观的影响到executionTimeMillis,咱们须要扫描的越少速度越快。
对于一个查询, 咱们最理想的状态是
nReturned=totalKeysExamined & totalDocsExamined=0
(cover index,仅仅使用到了index,无需文档扫描,这是最理想状态。)
或者
nReturned=totalKeysExamined=totalDocsExamined(须要具体状况具体分析)
(正常index利用,无多余index扫描与文档扫描。)
若是有sort的时候,为了使得sort不在内存中进行,咱们能够在保证nReturned=totalDocsExamined
的基础上,totalKeysExamined能够大于totalDocsExamined与nReturned,由于量级较大的时候内存排序很是消耗性能。
后面咱们会针对例子来进行分析。
那么又是什么影响到了totalKeysExamined与totalDocsExamined呢?就是Stage的类型,Stage的具体含义在上文中有说起,若是认真看的同窗就不难理解为什么Stage会影响到totalKeysExamined 和totalDocsExamined从而影响executionTimeMillis了。
此前有讲解过stage的类型,这里再简单列举下(具体意义请看上文)
COLLSCAN
IXSCAN
FETCH
SHARD_MERGE
SORT
LIMIT
SKIP
IDHACK
SHARDING_FILTER
COUNT
COUNTSCAN
COUNT_SCAN
SUBPLA
TEXT
PROJECTION
Fetch+IDHACK
Fetch+ixscan
Limit+(Fetch+ixscan)
PROJECTION+ixscan
SHARDING_FILTER+ixscan
等
COLLSCAN(全表扫),SORT(使用sort可是无index),不合理的SKIP,SUBPLA(未用到index的$or)
COUNT_SCAN
COUNTSCAN
表中数据以下(简单测试用例,仅10条数据,主要是对explain分析的逻辑进行解析):
{ "_id" : ObjectId("55b86d6bd7e3f4ccaaf20d70"), "a" : 1, "b" : 1, "c" : 1 } { "_id" : ObjectId("55b86d6fd7e3f4ccaaf20d71"), "a" : 1, "b" : 2, "c" : 2 } { "_id" : ObjectId("55b86d72d7e3f4ccaaf20d72"), "a" : 1, "b" : 3, "c" : 3 } { "_id" : ObjectId("55b86d74d7e3f4ccaaf20d73"), "a" : 4, "b" : 2, "c" : 3 } { "_id" : ObjectId("55b86d75d7e3f4ccaaf20d74"), "a" : 4, "b" : 2, "c" : 5 } { "_id" : ObjectId("55b86d77d7e3f4ccaaf20d75"), "a" : 4, "b" : 2, "c" : 5 } { "_id" : ObjectId("55b879b442bfd1a462bd8990"), "a" : 2, "b" : 1, "c" : 1 } { "_id" : ObjectId("55b87fe842bfd1a462bd8991"), "a" : 1, "b" : 9, "c" : 1 } { "_id" : ObjectId("55b87fe942bfd1a462bd8992"), "a" : 1, "b" : 9, "c" : 1 } { "_id" : ObjectId("55b87fe942bfd1a462bd8993"), "a" : 1, "b" : 9, "c" : 1 }
查询语句:
db.d.find({a:1,b:{$lt:3}}).sort({c:-1})
首先,咱们看看没有index时候的查询计划
"executionStats" : { "executionSuccess" : true, "nReturned" : 2, "executionTimeMillis" : 0, "totalKeysExamined" : 0, "totalDocsExamined" : 10, "executionStages" : { "stage" : "SORT", "nReturned" : 2, ... "sortPattern" : { "c" : -1 }, "memUsage" : 126, "memLimit" : 33554432, "inputStage" : { "stage" : "COLLSCAN", "filter" : { "$and" : [ { "a" : { "$eq" : 1 } }, { "b" : { "$lt" : 3 } } ] }, "nReturned" : 2, ... "direction" : "forward", "docsExamined" : 10 }
nReturned为2,符合的条件的返回为2条。
totalKeysExamined为0,没有使用index。
totalDocsExamined为10,扫描了全部记录。
executionStages.stage为SORT,未使用index的sort,占用的内存与内存限制为”memUsage” : 126, “memLimit” : 33554432。
executionStages.inputStage.stage为COLLSCAN,全表扫描,扫描条件为
"filter" : { "$and" : [ { "a" : { "$eq" : 1 } }, { "b" : { "$lt" : 3 } } ] },
很明显,没有index的时候,进行了全表扫描,没有使用到index,在内存中sort,很显然,和都是不可取的。
下面,咱们来对sort项c加一个索引
db.d.ensureIndex({c:1})
再来看看执行计划
"executionStats" : { "executionSuccess" : true, "nReturned" : 2, "executionTimeMillis" : 1, "totalKeysExamined" : 10, "totalDocsExamined" : 10, "executionStages" : { "stage" : "FETCH", "filter" : { "$and" : [ { "a" : { "$eq" : 1 } }, { "b" : { "$lt" : 3 } } ] }, "nReturned" : 2, ... "inputStage" : { "stage" : "IXSCAN", "nReturned" : 10, ... "keyPattern" : { "c" : 1 }, "indexName" : "c_1", "isMultiKey" : false, "direction" : "backward", "indexBounds" : { "c" : [ "[MaxKey, MinKey]" ] },
咱们发现,Stage没有了SORT,由于咱们sort字段有了index,可是因为查询仍是没有index,故totalDocsExamined仍是10,可是因为sort用了index,totalKeysExamined也是10,可是仅对sort排序作了优化,查询性能仍是同样的低效。
接下来, 咱们对查询条件作index(作多种index方案寻找最优)
咱们的查询语句依然是:
db.d.find({a:1,b:{$lt:3}}).sort({c:-1})
使用db.d.ensureIndex({b:1,a:1,c:1})
索引的执行计划:
"executionStats" : { "executionSuccess" : true, "nReturned" : 2, "executionTimeMillis" : 0, "totalKeysExamined" : 4, "totalDocsExamined" : 2, "executionStages" : { "stage" : "SORT", "nReturned" : 2, ... "sortPattern" : { "c" : -1 }, "memUsage" : 126, "memLimit" : 33554432, "inputStage" : { "stage" : "FETCH", "nReturned" : 2, ... "inputStage" : { "stage" : "IXSCAN", "nReturned" : 2, ... "keyPattern" : { "b" : 1, "a" : 1, "c" : 1 }, "indexName" : "b_1_a_1_c_1", "isMultiKey" : false, "direction" : "forward", "indexBounds" : { "b" : [ "[-inf.0, 3.0)" ], "a" : [ "[1.0, 1.0]" ], "c" : [ "[MinKey, MaxKey]" ] },
咱们能够看到
nReturned为2,返回2条记录
totalKeysExamined为4,扫描了4个index
totalDocsExamined为2,扫描了2个docs
此时nReturned=totalDocsExamined<totalKeysExamined,不符合咱们的指望。
且executionStages.Stage为Sort,在内存中进行排序了,也不符合咱们的指望
使用db.d.ensureIndex({a:1,b:1,c:1})
索引的执行计划:
"executionStats" : { "executionSuccess" : true, "nReturned" : 2, "executionTimeMillis" : 0, "totalKeysExamined" : 2, "totalDocsExamined" : 2, "executionStages" : { "stage" : "SORT", "nReturned" : 2, ... "sortPattern" : { "c" : -1 }, "memUsage" : 126, "memLimit" : 33554432, "inputStage" : { "stage" : "FETCH", "nReturned" : 2, ... "inputStage" : { "stage" : "IXSCAN", "nReturned" : 2, ... "keyPattern" : { "a" : 1, "b" : 1, "c" : 1 }, "indexName" : "a_1_b_1_c_1", "isMultiKey" : false, "direction" : "forward", "indexBounds" : { "a" : [ "[1.0, 1.0]" ], "b" : [ "[-inf.0, 3.0)" ], "c" : [ "[MinKey, MaxKey]" ] },
咱们能够看到
nReturned为2,返回2条记录
totalKeysExamined为2,扫描了2个index
totalDocsExamined为2,扫描了2个docs
此时nReturned=totalDocsExamined=totalKeysExamined,符合咱们的指望。看起来很美吧?
可是,可是,可是!重要的事情说三遍!executionStages.Stage为Sort,在内存中进行排序了,这个在生产环境中尤为是在数据量较大的时候,是很是消耗性能的,这个千万不能忽视了,咱们须要改进这个点。
最后,咱们要在nReturned=totalDocsExamined的基础上,让排序也使用index,咱们使用db.d.ensureIndex({a:1,c:1,b:1})
索引,执行计划以下:
"executionStats" : { "executionSuccess" : true, "nReturned" : 2, "executionTimeMillis" : 0, "totalKeysExamined" : 4, "totalDocsExamined" : 2, "executionStages" : { "stage" : "FETCH", "nReturned" : 2, ... "inputStage" : { "stage" : "IXSCAN", "nReturned" : 2, ... "keyPattern" : { "a" : 1, "c" : 1, "b" : 1 }, "indexName" : "a_1_c_1_b_1", "isMultiKey" : false, "direction" : "backward", "indexBounds" : { "a" : [ "[1.0, 1.0]" ], "c" : [ "[MaxKey, MinKey]" ], "b" : [ "(3.0, -inf.0]" ] }, "keysExamined" : 4, "dupsTested" : 0, "dupsDropped" : 0, "seenInvalidated" : 0, "matchTested" : 0
咱们能够看到
nReturned为2,返回2条记录
totalKeysExamined为4,扫描了4个index
totalDocsExamined为2,扫描了2个docs
虽然不是nReturned=totalKeysExamined=totalDocsExamined,可是Stage无Sort,即利用了index进行排序,而非内存,这个性能的提高高于多扫几个index的代价。
综上能够有一个小结论,当查询覆盖精确匹配,范围查询与排序的时候,
{精确匹配字段,排序字段,范围查询字段}
这样的索引排序会更为高效。
执行计划分析一文,到此便告一段落了,但愿你们可以对于MongoDB的执行计划有所了解。
周李洋,社区经常使用ID eshujiushiwo,关注Mysql与MongoDB技术,数据架构,服务器架构等,现就任于DeNA,mongo-mopre,mongo-mload做者,任CSDN mongodb版主,MongoDB上海用户组发起人,MongoDB官方翻译组核心成员,MongoDB中文站博主,MongoDB Contribution Award得到者,MongoDB Days Beijing 2014演讲嘉宾。 联系方式:378013446 MongoDB上海用户组:192202324 欢迎交流。