MongoDB干货系列2-MongoDB执行计划分析详解(3)
2016-07-25 00:00
405 查看
写在之前的话
作为近年最为火热的文档型数据库,MongoDB受到了越来越多人的关注,但是由于国内的MongoDB相关技术分享屈指可数,不少朋友向我抱怨无从下手。《MongoDB干货系列》将从实际应用的角度来进行MongoDB的一些列干货的分享,将覆盖调优,troubleshooting等方面,希望能对大家带来帮助。
如果希望了解更多MongoDB基础的信息,还请大家Google下。
要保证数据库处于高效、稳定的状态,除了良好的硬件基础、高效高可用的数据库架构、贴合业务的数据模型之外,高效的查询语句也是不可少的。那么,如何查看并判断我们的执行计划呢?我们今天就来谈论下MongoDB的执行计划分析。
引子
MongoDB 3.0之后,explain的返回与使用方法与之前版本有了不少变化,介于3.0之后的优秀特色,本文仅针对MongoDB 3.0+的explain进行讨论。现版本explain有三种模式,分别如下:
queryPlanner
executionStats
allPlansExecution
由于文章字数原因,本系列将分为三个部分。
本文是MongoDB执行计划分析详解的最后一个部分,我们将对该如何分析exlain信息进行详细解读,并将针对实例进行explain分析详解。
正文
对Explain返回逐层分析
第一层,executionTimeMillis。
首先,最为直观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所用时间
这三个值我们都希望越少越好,那么是什么影响这这三个返回值呢?
抛开硬件因素等不谈,我们来进行下一层的剥离。
第二层,index与document扫描数与查询返回条目数
这里主要谈3个返回项,nReturned,totalKeysExamined与totalDocsExamined,分别代表该条查询返回的条目、索引扫描条目和文档扫描条目。很好理解,这些都直观的影响到executionTimeMillis,我们需要扫描的越少速度越快。
对于一个查询, 我们最理想的状态是
nReturned=totalKeysExamined & totalDocsExamined=0
(cover index,仅仅使用到了index,无需文档扫描,这是最理想状态。)
或者
nReturned=totalKeysExamined=totalDocsExamined(需要具体情况具体分析)
(正常index利用,无多余index扫描与文档扫描。)
如果有sort的时候,为了使得sort不在内存中进行,我们可以在保证nReturned=totalDocsExamined
的基础上,totalKeysExamined可以大于totalDocsExamined与nReturned,因为量级较大的时候内存排序非常消耗性能。
后面我们会针对例子来进行分析。
第三层,Stage状态分析
那么又是什么影响到了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+IDHACKFetch+ixscan
Limit+(Fetch+ixscan)
PROJECTION+ixscan
SHARDING_FILTER+ixscan
等
不希望看到包含如下的stage:
COLLSCAN(全表扫),SORT(使用sort但是无index),不合理的SKIP,SUBPLA(未用到index的$or)对于count查询,希望看到的有:
COUNT_SCAN不希望看到的有:
COUNTSCANExplain分析实例
表中数据如下(简单测试用例,仅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的代价。
综上可以有一个小结论,当查询覆盖精确匹配,范围查询与排序的时候,
{精确匹配字段,排序字段,范围查询字段}这样的索引排序会更为高效。
重要提醒:开源中国JAVA交流群:109405598,欢迎各位OSCer加入
相关文章推荐
- 分享微信开发Html5轻游戏中的几个坑
- 如何在 Fedora 上安装 MongoDB 服务器
- PHP添加yaf xhprof mongodb 同理
- mongodb安装
- 如何在 Ubuntu 上安装 MongoDB
- 信息安全聚合 Sec-News 的重构之路
- Ruby on Rails框架程序连接MongoDB的教程
- perl操作MongoDB报错undefined symbol: HeUTF8解决方法
- 浅析SQL Server中的执行计划缓存(上)
- MYSQL explain 执行计划
- SQL参数化查询的另一个理由 命中执行计划
- SqlServer 执行计划及Sql查询优化初探
- C#中使用1.7版本驱动操作MongoDB简单例子
- 使用zabbix监控mongodb的方法
- Node.js的MongoDB驱动Mongoose基本使用教程
- MongoDB系列教程(四):设置用户访问权限
- SQLSERVER中得到执行计划的两种方式
- ASP.NET MVC4使用MongoDB制作相册管理
- MSSQL优化之探索MSSQL执行计划(转)
- node.js连接mongoDB数据库 快速搭建自己的web服务