优化服务器硬件
mongodb内存使用方式
- mongodb使用内存映射文件I/O访问存储文件,系统将按需把需要的文件映射到RAM中。
- 工作集大小。这个大小代表着mongodb实例中存储的数据量。对大多数mongodb实例而言,常规操作通常只会访问到其中一部分数据。了解工作集大小可以帮助正确计算硬件大小。
- 确定硬件是最需要注意的是RAM的大小。不需要在服务器上安装512G的内存,因为可以通过分片将数据分散到多台服务器。
评估查询性能
mongodb有两个用于优化查询性能的工具:
- mongodb分析器:查找查询性能不佳的查询以及选择查询用于进一步检查的好工具。
explain():研究单个查询,通过explain()可以判断查询的执行性能。
mongodb分析器
分析器级别
MongoDB数据库分析器可以在实例级别打开,也可以在数据库级别打开。
MongoDB数据库分析器可以设置三个级别:- 0 - 分析器处在关闭状态,不收集任何数据
- 1 - 仅仅收集较慢操作的分析数据。默认情况下如果一个操作花费的时间超过100ms,就认为是较慢的操作。
2 - 收集所有数据库操作的分析数据。
system.profile集合
MongoDB数据库分析器收集的数据将存放在system.profile集合中。system.profile是一个固定大小的集合,数据将循环写入该集合。当用完所有分配的空间后,MongoDB将会覆盖集合中最老的文档。默认情况下,system.profile集合大小为4M。开启分析器后,对应的数据库下创建system.profile集合,可以通过以下方式查看该集合信息:
> db.system.profile.stats()
{
"ns" : "test.system.profile",
"size" : 0,
"count" : 0,
"storageSize" : 4096,
"capped" : true,
"max" : -1,
"maxSize" : 1048576,
"sleepCount" : 0,
"sleepMS" : 0,
"wiredTiger" : {
"metadata" : {
"formatVersion" : 1
},
.......
}
启用和禁用mongodb分析器
启用(数据库级别)
> use test
switched to db test
> db.setProfilingLevel(1)
{ "was" : 0, "slowms" : 100, "ok" : 1 }
- “slowms”:100 指超过100ms的查询都被列为慢查询
禁用 (数据库级别)
db.setProfilingLevel(0)
{ "was" : 1, "slowms" : 100, "ok" : 1 }
指定特定时间的查询启用分析器
> use test
switched to db test
> db.setProfilingLevel(1,500)
{ "was" : 0, "slowms" : 100, "ok" : 1 }
> db.getProfilingStatus()
{ "was" : 1, "slowms" : 500 }
查看分析器Level
> db.getProfilingLevel()
1
查找慢查询
db.system.profile.find()
{
"op" : "query",
"ns" : "test.col",
"query" : {
"find" : "col",
"filter" : {
}
},
"keysExamined" : 0,
"docsExamined" : 2,
"cursorExhausted" : true,
"numYield" : 0,
"locks" : {
"Global" : {
"acquireCount" : {
"r" : NumberLong(2)
}
},
"Database" : {
"acquireCount" : {
"r" : NumberLong(1)
}
},
"Collection" : {
"acquireCount" : {
"r" : NumberLong(1)
}
}
},
"nreturned" : 2,
"responseLength" : 165,
"protocol" : "op_command",
"millis" : 0,
"planSummary" : "COLLSCAN",
"execStats" : {
"stage" : "COLLSCAN",
"nReturned" : 2,
"executionTimeMillisEstimate" : 0,
"works" : 4,
"advanced" : 2,
"needTime" : 1,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"direction" : "forward",
"docsExamined" : 2
},
"ts" : ISODate("2016-12-27T05:35:27.941Z"),
"client" : "127.0.0.1",
"appName" : "MongoDB Shell",
"allUsers" : [ ],
"user" : ""
}
查找执行时间大于某个特定时间的查询
db.system.profile.find({millis:{$gt:10}}).sort({millis:-1})
system.profile只是一个普通的集合,在普通集合的查询方式在此都可用。可结合更多的字段进行筛选。
增大分析集合的大小
顺序如下:
1. 禁用目标数据库上的分析器
db.setProfilingLevel(0)
- 删除现有的system.profile集合
db.system.profile.drop()
- 创建新的分析器集合system.profile,并使用目标字节大小作为参数。通过db.system.profile.stats()命令可以看到”capped” : true,说明该集合是一个固定大小的集合。
db.createCollection('system.profile',{capped:true,size:50*1024*1024})
- 成功创建集合后,重新启用分析器
db.setProfilingLevel(1)
使用explain分析特定的查询
插入测试数据
>use test
switched to db test
> for (var i=1;i<=10000;i++) db.col.insert({a:i,b:i+1})
WriteResult({ "nInserted" : 1 })
执行计划分析详解
现版本explain有三种模式,分别如下:
- queryPlanner
- executionStats
- allPlansExecution
queryPlanner
queryPlanner是现版本explain的默认模式,queryPlanner模式下并不会去真正进行query语句查询,而是针对query语句进行执行计划分析并选出winning plan。
> db.test.find({a:{$gt:8000}}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.test",
"indexFilterSet" : false,
"parsedQuery" : {
"a" : {
"$gt" : 8000
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"a" : 1
},
"indexName" : "a_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"a" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"a" : [
"(8000.0, inf.0]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "iz2zecqalqk7c59mfkog6gz",
"port" : 27017,
"version" : "3.4.1",
"gitVersion" : "5e103c4f5583e2566a45d740225dc250baacfbd7"
},
"ok" : 1
}
关键字段解释:
1. explain.queryPlanner:queryPlanner的返回
2. explain.queryPlanner.namespace:该值返回的是该query所查询的表名
3. explain.queryPlanner.indexFilterSet:针对该query是否有indexfilter
4. explain.queryPlanner.winningPlan:查询优化器针对该query所返回的最优执行计划的详细内容
5. explain.queryPlanner.winningPlan.stage:最优执行计划的stage,这里返回是FETCH,可以理解为通过返回的index位置去检索具体的文档
如下几类介绍
COLLSCAN:全表扫描
IXSCAN:索引扫描
FETCH:根据索引去检索指定document
SHARD_MERGE:将各个分片返回数据进行merge
- explain.queryPlanner.winningPlan.inputStage:explain.queryPlanner.winningPlan.stage的child stage,此处是IXSCAN,表示进行的是index scanning
- explain.queryPlanner.winningPlan.keyPattern:所扫描的index内容,此处是a:1
- explain.queryPlanner.winningPlan.indexName:winning plan所选用的index
- explain.queryPlanner.winningPlan.isMultiKey:
是否是Multikey,此处返回是false,如果索引建立在array上,此处将是true
executionStats
> db.test.find({a:{$gt:8000}}).explain('executionStats')
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.test",
"indexFilterSet" : false,
"parsedQuery" : {
"a" : {
"$gt" : 8000
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"a" : 1
},
"indexName" : "a_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"a" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"a" : [
"(8000.0, inf.0]"
]
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1999,
"executionTimeMillis" : 5,
"totalKeysExamined" : 1999,
"totalDocsExamined" : 1999,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 1999,
"executionTimeMillisEstimate" : 10,
"works" : 2000,
"advanced" : 1999,
"needTime" : 0,
"needYield" : 0,
"saveState" : 15,
"restoreState" : 15,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 1999,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 1999,
"executionTimeMillisEstimate" : 0,
"works" : 2000,
"advanced" : 1999,
"needTime" : 0,
"needYield" : 0,
"saveState" : 15,
"restoreState" : 15,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"a" : 1
},
"indexName" : "a_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"a" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"a" : [
"(8000.0, inf.0]"
]
},
"keysExamined" : 1999,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
},
"serverInfo" : {
"host" : "iz2zecqalqk7c59mfkog6gz",
"port" : 27017,
"version" : "3.4.1",
"gitVersion" : "5e103c4f5583e2566a45d740225dc250baacfbd7"
},
"ok" : 1
}
executionStats.executionSuccess:是否执行成功
executionStats.nReturned:查询的返回条数
executionStats.executionTimeMillis:整体执行时间
executionStats.totalKeysExamined:索引扫描次数
executionStats.totalDocsExamined:document扫描次数
executionStats.executionStages.stage:这里是FETCH去扫描对于documents
executionStats.executionStages.nReturned:由于是FETCH,所以这里该值与executionStats.nReturned一致
executionStats.executionStages.docsExamined:与executionStats.totalDocsExamined一致
allPlansExecution
allPlansExecution模式是将所有的执行计划均进行executionStats模式的操作
对Explain返回逐层分析
第一层,executionTimeMillis
首先,最为直观explain返回值是executionTimeMillis值,指的是我们这条语句的执行时间,这个值当然是希望越少越好
有3个executionTimeMillis,分别是:
- executionStats.executionTimeMillis
该query的整体查询时间 - executionStats.executionStages.executionTimeMillis
该查询根据index去检索document获取具体数据的时间 - executionStats.executionStages.inputStage.executionTimeMillis
该查询扫描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状态分析
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
对于count查询,希望看到的有:
COUNT_SCAN
不希望看到的有:
COUNTSCAN