一 概念
在关系型数据库中无论哪种数据库,都提供了SQL剖析工具,用来解决SQL低下的问题。在MongoDB中,也有相应的策略来实现剖析。mongoDB系统了explain()方法,用来查看其执行计划和其统计信息。
二 explain三种模式
1、queryPlanner
queryPlanner是explain的默认模式,queryPlanner模式下并不会去真正进行操作语句的执行,而是针对query语句进行执行计划分析并选出winning plan
1)namespace:
操作语句所针对的表
2)indexFilterSet:
布尔值,该操作是否指定indexFilter
3)winningPlan:
查询优化器对该query返回的最优执行计划的详细内容
4)winningPlan.stage
操作阶段的策略
5)winningPlan.inputStage/winningPlan.inputStages
子操作阶段
6)winningPlan.inputStage.stage
子操作阶段的策略
7)winningPlan.inputStage.keyPattern
扫描的index内容
8)winningPlan.inputStage.indexName
winning plan所选用的index
9)winningPlan.inputStage.isMultiKey
是否是Multikey,此处返回是false,如果索引建立在array上,此处将是true
10)winningPlan.inputStage.direction
此query的查询顺序,与sort有关,sort:1为forward,sort:-1为backward
11)winningPlan.inputStage.indexBounds:
winningplan所扫描的索引范围
12)rejectePlans
其他的执行计划
2、executionStats
mongoDB运行查询优化器对当前的查询进行评估并选择一个最佳的查询计划进行执行。
1)executionSuccess:
是否执行成功
2)nReturned:
查询的返回条数
3)executionTimeMillis:
整体执行时间
4)totalKeysExamined:
索引扫描次数
5)totalDocsExamined:
document扫描文档数目
6)executionStages:
执行阶段的全部详情
7)executionStages.works
8)executionStages.advanced
9)executionStages.needTime
10)executionStages.needYield
存储层产生锁的次数
11)executionStages.isEOF
指定执行阶段是否结束
12)executionStages.inputStage.keysExamined
执行过程中扫描索引数据的次数
13)executionStages.inputStage.docsExamined
执行阶段扫描文档数据的次数
3、allPlansExecution
该模式是前两种模式的更细化,会包括上述两种模式的所有信息。
即按照最佳的执行计划列出统计信息,还会列出一些候选的执行计划。
三 IndexFilter与Stage详解
1、IndexFilter
1)概念
IndexFilter决定了查询优化器对于某一类型的查询将如何使用index,indexFilter仅影响查询优化器对于该类查询可以尝试用哪些index的执行计划分析,查询优化器还是根据分析情况选择最优计划。
2)IndexFilter的创建
可以通过如下命令为某一个collection建立indexFilter
db.runCommand(
{
planCacheSetFilter: <collection>,
query: <query>,
sort: <sort>,
projection: <projection>,
indexes: [ <index1>, <index2>, ...]
}
)
db.runCommand(
{
planCacheSetFilter: "orders",
query: { status: "A" },
indexes: [
{ cust_id: 1, status: 1 },
{ status: 1, order_date: -1 }
]
}
)
以上针对orders表建立了一个indexFilter,indexFilter指定了对于orders表只有status条件(仅对status进行查询,无sort等)的查询的indexes,故下图的查询语句的查询优化器仅仅会从{cust_id:1,status:1}和{status:1,order_date:-1}中进行winning plan的选择
db.orders.find( { status: "D" } )
db.orders.find( { status: "P" } )
3)IndexFilter的列表
可以通过如下命令展示某一个collecton的所有indexFilter
db.runCommand( { planCacheListFilters: <collection> } )
4)IndexFilter的删除
可以通过如下命令对IndexFilter进行删除
db.runCommand(
{
planCacheClearFilters: <collection>,
query: <query pattern>,
sort: <sort specification>,
projection: <projection specification>
}
)
2、Stage
如queryPlanner.winningPlan.stage和queryPlanner.winningPlan.inputStage等。
COLLSCAN:全表扫描
IXSCAN:索引扫描
FETCH:根据索引去检索指定document
SHARD_MERGE:将各个分片返回数据进行merge
SORT:表明在内存中进行了排序(与老版本的scanAndOrder:true一致)
LIMIT:使用limit限制返回数
SKIP:使用skip进行跳过
IDHACK:针对_id进行查询
SHARDING_FILTER:通过mongos对分片数据进行查询
COUNT:利用db.coll.explain().count()之类进行count运算
COUNTSCAN:count不使用用Index进行count时的stage返回
COUNT_SCAN:count使用了Index进行count时的stage返回
SUBPLA:未使用到索引的$or查询的stage返回
TEXT:使用全文索引进行查询时候的stage返回
PROJECTION:限定返回字段时候stage的返回
四 结果分析
1、executionTimeMillis分析
executionStats模式中,有3个执行时间:
executionTimeMillis:该query的整体查询时间
executionStages.executionTimeMillis:该查询根据index去检索document获取数据的时间
executionStages.inputStage.executionTimeMillis:该查询扫描index行所用时间
这三个值越小,该查询的性能最好
2、nReturned、totalKeysExamined与totalDocsExamined分析
这三个返回值分别代表该条查询返回的条目、索引扫描条目和根据索引去检索文档的扫描条目。这些都直观的影响到executionTimeMillis值的大小。
对于一个查询, 我们最理想的状态是:
1)索引覆盖
nReturned=totalKeysExamined & totalDocsExamined=0
即仅仅使用到了index,无需文档扫描,这是最理想状态。
2)正常使用索引
nReturned=totalKeysExamined=totalDocsExamined
即正常index利用,无多余index扫描与文档扫描。
如果有sort的时候,为了使得sort不在内存中进行,我们可以在保证nReturned=totalDocsExamined的基础上,totalKeysExamined可以大于totalDocsExamined与nReturned,因为量级较大的时候内存排序非常消耗性能。
3、Stage状态分析
Stage类型会影响到totalKeysExamined和totalDocsExamined,从而影响executionTimeMillis值的大小。
对于普通查询,我们最希望看到的组合有这些:
Fetch+IDHACK
Fetch+ixscan
Limit+(Fetch+ixscan)
PROJECTION+ixscan
SHARDING_FILTER+ixscan
不希望看到包含如下的stage:
COLLSCAN(全表扫)
SORT(使用sort但是无index)
不合理的SKIP
SUBPLA(未用到index的$or)
对于count查询,希望看到的有:
COUNT_SCAN,
不希望看到的有:
COUNTSCAN。
当查询覆盖精确匹配,范围查询与排序的时候,{精确匹配字段,排序字段,范围查询字段}这样的索引排序会更为高效。
五 命令
1、普通查询
db.collection.explain()
2、游标查询
cursor.explain()
3、聚合查询
db.collection.aggregate([{pipelines}],{explain:true})
六 示例
1、查看不同顺序的混合索引的最优解
1)数据
{ "_id" : 1, "item" : "f1", type: "food", quantity: 500 }
{ "_id" : 2, "item" : "f2", type: "food", quantity: 100 }
{ "_id" : 3, "item" : "p1", type: "paper", quantity: 200 }
{ "_id" : 4, "item" : "p2", type: "paper", quantity: 150 }
{ "_id" : 5, "item" : "f3", type: "food", quantity: 300 }
{ "_id" : 6, "item" : "t1", type: "toys", quantity: 500 }
{ "_id" : 7, "item" : "a1", type: "apparel", quantity: 250 }
{ "_id" : 8, "item" : "a2", type: "apparel", quantity: 400 }
{ "_id" : 9, "item" : "t2", type: "toys", quantity: 50 }
{ "_id" : 10, "item" : "f4", type: "food", quantity: 75 }
2)根据查询条件建立索引
db.inventory.createIndex( { quantity: 1, type: 1 } )
db.inventory.createIndex( { type: 1, quantity: 1 } )
3)分别使用不同的索引查看执行计划
db.inventory.find({ quantity: { $gte: 100, $lte: 300 }, type: "food" }).hint({ quantity: 1, type: 1 }).explain("executionStats")
{
"queryPlanner" : {
...
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"quantity" : 1,
"type" : 1
},
...
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 2,
"executionTimeMillis" : 0,
"totalKeysExamined" : 5,
"totalDocsExamined" : 2,
"executionStages" : {
...
}
},
...
}
MongoDB扫描5个索引条目并返回2个符合条件的Document
db.inventory.find({ quantity: { $gte: 100, $lte: 300 }, type: "food" }).hint({ type: 1, quantity: 1 }).explain("executionStats")
{
"queryPlanner" : {
...
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"type" : 1,
"quantity" : 1
},
...
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 2,
"executionTimeMillis" : 0,
"totalKeysExamined" : 2,
"totalDocsExamined" : 2,
"executionStages" : {
...
}
},
...
}
MongoDB扫描2个索引条目并返回2个符合条件的Document
比较两次查询的结果,索引2比索引1更有效。
参考链接:
http://www.mongoing.com/eshu_explain1
https://docs.mongodb.com/manual/tutorial/analyze-query-plan/