--MongoDb整体性能优化及基于查看执行计划的索引优化(含物理设计,版本选择,范式策略和建议)
【官网】:https://www.mongodb.com/
应用场景
很多时候MongoDb用上了,但还是遇到性能问题。这时我们就需要至少考虑以下情况了: 1)内存是否充足. 2)(BTree 树)索引不命中所占百分比. 3)全局写入锁占用了机器多少时间。发生全局写入锁时,所有查询都将等待,直到锁解除. 4)等待处理的查询请求队列大小. 5)当前连接数.基础资源
无
使用须知
Mongodb性能调优不是最终或最有效的手段,最高效的方法是做出好的物理设计。而什么样的物理设计适合Mongodb,最后还是由当前业务及业务未来发展趋势决定的。最后送给大家一句话“好的性能不是调出来的,更多是设计出来的”!
配置步骤
A)MongoDb查询慢,性能不好的监测点。
mongostat:
A1)faults/s:每秒访问失败数,
即数据被交换出物理内存,放到SWAP。若过高(一般超过100),则意味着内存不足。
vmstat & iostat & iotop
[注]
si:每秒从磁盘读入虚拟内存的大小,若大于0,表示物理内存不足。
so:每秒虚拟内存写入磁盘的大小,若大于0,同上。
A2) idx miss %:BTree 树未命中的比例,即索引不命中所占百分比.
若过高,则意味着索引建立或使用不合理。
db.serverStatus()
indexCounters” : {
“btree” : {
“accesses” : 2821726, #索引被访问数
“hits” : 2821725, #索引命中数
“misses” : 1, #索引偏差数
“resets” : 0, #复位数
“missRatio” : 3.543930204420982e-7 #未命中率
}
A3)locked %:全局写入锁占用了机器多少时间.
当发生全局写入锁时,所有查询操作都将等待,直到写入锁解除。
若过高(一般超过50%),则意味着程序存在问题。
db.currentOp()
{
“inprog” : [ ],
“fsyncLock” : 1, #为1表示MongoDB的fsync进程(负责将写入改变同步到磁盘)不允许其他进程执行写数据操作
“info” : “use db.fsyncUnlock() to terminate the fsync write/snapshot lock”
}
A4)q r|w :等待处理的查询请求队列大小.
若过高,则意味着查询会过慢。
db.serverStatus()
“currentQueue” : {
“total” : 1024, #当前需要执行的队列
“readers” : 256, #读队列
“writers” : 768 #写队列
}
A5)conn :当前连接数.
高并发下,若连接数上不去,则意味着Linux系统内核需要调优。
db.serverStatus()
“connections” : {
“current” : 3, #当前连接数
“available” : 19997 #可用连接数
}
A6)连接数使用内存.
cat /proc/$(pidof mongod)/limits | grep stack | awk -F ‘size‘ ‘{print int($NF)/1024}‘
将连接数使用Linux栈内存设小,默认为10MB(10240)
shell> ulimit -s 1024
B)查看执行计划,并分析结果。
B1)优化器的设置。
db.setProfilingLevel(2);
0 – 不开启
1 – 记录慢命令 (默认为>100ms)
2 – 记录所有命令
info: #本命令的详细信息
reslen: #返回结果集的大小
nscanned: #本次查询扫描的记录数
nreturned: #本次查询实际返回的结果集
millis: #该命令执行耗时(毫秒)
B2)监控一个集合时一般关注的信息有哪些?.
- 集合 collect_x 未建立有效索引(建议考虑使用组合索引)
- 存在大量慢查询,均为collect_x读操作,且响应超过1秒
- 每次读操作均为全集合扫描,意味着耗用CPU(25% * 8核)
- 每次返回的记录字节数近1KB,建议过滤不必要的字段,提高传输效率。
B3)如何查询执行计划?
>db.Book.find({CharsCount: “200000”}).hint({CharsCount:1 }).explain();
B4)分析查询计划。
分析案例1)
[注]
cursor: 返回游标类型(BasicCursor 或 BtreeCursor)
nscanned: 被扫描的文档数量
n: 返回的文档数量
millis: 耗时(毫秒)
indexBounds: 所使用的索引.
分析案例2)
{ "queryPlanner" : { "plannerVersion" : 1, "namespace" : "myDatabase.myColl", "indexFilterSet" : false, "parsedQuery" : ... "winningPlan" : { "stage" : "LIMIT", "limitAmount" : 1, "inputStage" : { "stage" : "FETCH", "filter" : ..., "inputStage" : { "stage" : "IXSCAN", "keyPattern" : { "_id" : 1 }, "indexName" : "_id_", ... "direction" : "backward", "indexBounds" : { "_id" : [ "[MaxKey, MinKey]" ] } } } }, "rejectedPlans" : ..., }, "executionStats" : { "executionSuccess" : true, "nReturned" : 1, "executionTimeMillis" : 0, "totalKeysExamined" : 8, "totalDocsExamined" : 8, "executionStages" : { "stage" : "LIMIT", ... "inputStage" : { "stage" : "FETCH", ... "inputStage" : { "stage" : "IXSCAN", ... "direction" : "backward", "indexBounds" : { "_id" : [ "[MaxKey, MinKey]" ] }, "keysExamined" : 8, ... } } } }, "serverInfo" : ..., "ok" : 1 }
[注]
explain()结果分为四部分:queryPlanner、executionStats、serverInfo、ok,仅关注queryPlanner、executionStats这两部分。
executionStats就是执行queryPlanner.winningPlan这个计划时的统计信息。
1)从indexBounds看到good query在索引扫描(IXSCAN)阶段,使用的索引是_id主键索引。
"indexName" : "_id_",
"indexBounds" : {
2)从IXSCAN这个阶段的keysExamined统计可以解释为什么good query执行的这么快,只扫描了8条数据。
//"keysExamined" : 8,
B5)查询计划explain结果结构描述.
对queryPlanner分析
queryPlanner: queryPlanner的返回
queryPlanner.namespace:该值返回的是该query所查询的表
queryPlanner.indexFilterSet:针对该query是否有indexfilter
queryPlanner.winningPlan:查询优化器针对该query所返回的最优执行计划的详细内容。
queryPlanner.winningPlan.stage:最优执行计划的stage,这里返回是FETCH,可以理解为通过返回的index位置去检索具体的文档(stage有数个模式,将在后文中进行详解)。
queryPlanner.winningPlan.inputStage:用来描述子stage,并且为其父stage提供文档和索引关键字。
queryPlanner.winningPlan.stage的child stage,此处是IXSCAN,表示进行的是index scanning。
queryPlanner.winningPlan.keyPattern:所扫描的index内容,此处是did:1,status:1,modify_time: -1与scid : 1
queryPlanner.winningPlan.indexName:winning plan所选用的index。
queryPlanner.winningPlan.isMultiKey是否是Multikey,此处返回是false,如果索引建立在array上,此处将是true。
queryPlanner.winningPlan.direction:此query的查询顺序,此处是forward,如果用了.sort({modify_time:-1})将显示backward。
queryPlanner.winningPlan.indexBounds:winningplan所扫描的索引范围,如果没有制定范围就是[MaxKey, MinKey],这主要是直接定位到mongodb的chunck中去查找数据,加快数据读取。
queryPlanner.rejectedPlans:其他执行计划(非最优而被查询优化器reject的)的详细返回,其中具体信息与winningPlan的返回中意义相同,故不在此赘述。
对executionStats返回逐层分析
第一层,executionTimeMillis
最为直观explain返回值是executionTimeMillis值,指的是我们这条语句的执行时间,这个值当然是希望越少越好。
其中有3个executionTimeMillis,分别是:
executionStats.executionTimeMillis
该query的整体查询时间。
executionStats.executionStages.executionTimeMillisEstimate
该查询根据index去检索document获得2001条数据的时间。
executionStats.executionStages.inputStage.executionTimeMillisEstimate
该查询扫描2001行index所用时间。
第二层,index与document扫描数与查询返回条目数
这个主要讨论3个返回项,nReturned、totalKeysExamined、totalDocsExamined,分别代表该条查询返回的条目、索引扫描条目、文档扫描条目。
这些都是直观地影响到executionTimeMillis,我们需要扫描的越少速度越快。
对于一个查询,我们最理想的状态是:
nReturned=totalKeysExamined=totalDocsExamined
第三层,stage状态分析
那么又是什么影响到了totalKeysExamined和totalDocsExamined?是stage的类型。类型列举如下:
COLLSCAN:全表扫描
IXSCAN:索引扫描
FETCH:根据索引去检索指定document
SHARD_MERGE:将各个分片返回数据进行merge
SORT:表明在内存中进行了排序
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的返回
对于普通查询,我希望看到stage的组合(查询的时候尽可能用上索引):
Fetch+IDHACK
Fetch+ixscan
Limit+(Fetch+ixscan)
PROJECTION+ixscan
SHARDING_FITER+ixscan
COUNT_SCAN
不希望看到包含如下的stage:
COLLSCAN(全表扫描),SORT(使用sort但是无index),不合理的SKIP,SUBPLA(未用到index的$or),COUNTSCAN(不使用index进行count)
常见问题
快速入门
A)MongoDb引擎版本的选择.
-
Mongodb 3.0.X版本性能较Mongodb 2.0.X有7-10倍提升,引入WiredTiger新引擎,同时支持MMAPv1内存映射引擎
备注:若更换新引擎,则之前使用旧引擎建立的DB数据库无法使用。 建议先通过Mongodb的同步机制,将旧引擎建立的DB数据同步到从库, 且从库使用新引擎.
选择 Windows 2008 R2 x64 或 Linux x64,Linux版本性能优于 Windows,建议基于Linux系统进行架构选型
根据RHEL版本号选择Mongodb相应Linux版本
Mongodb Driver 与 Mongodb 版本一致
B)Mongodb的集合物理设计怎么做才有利于提升性能(范式,反范式,折中)?.
范式: 更新时速度快,只更新一条(因为遵循一事一地原则).
反范式: 查询时速度快,只查询一条(或者关联更少),因为都在个结构中。
折中: 对于静态的(无法修改或者几乎不需要修改)的数据进行反范式设计(旨在提高查询效率),对于经常更新的则遵从范式。
C)如何设计并创建索引?
c1)在查询条件、排序条件、统计条件的字段上选择创建索引.
示例: db.Book.ensureIndex({ BookPrice:1,VisitCount:500} , {backgroud:true});
最新或最近记录查询,结合业务需要正确使用索引方向:逆序或顺序
建议索引建立操作置于后台运行,降低影响
实际应用过程中多考虑使用复合索引
使用limit()限定返回结果集的大小,减少数据库服务器的资源消耗,以及网络传输的数据量
颗粒度越小越好,也就是值的分布比较均匀,重复度比较小。
db.posts.find().sort({ts:-1}).limit(10);
c2)只查询使用到的字段,而不查询所有字段.
参考语法:
db.collection.find(query, projection);//projection是投影,也就是返回的列。
db.Book.find({"_id":"0bd937c891bd3135e02"},{"BookName":true,"PublishDate":true}); //注:对于_id来说,不管有没有设置是否返回都会返回。
D)分布式环境下考虑使用MapReduce。
基于Mongodb分布式集群做数据分析时,MapReduce性能优于count、distinct、group等聚合函数
E)考虑使用Capped Collections (固定长度,环形存储,过期后会覆盖之前的).
-
Capped Collections比普通Collections的读写效率高
db.createCollection(“mycoll”, {capped:true, size:100000});
例:system.profile 是一个Capped Collection。
注意:
固定大小;Capped Collections 必须事先创建,并设置大小。
Capped Collections可以insert和update操作;不能delete操作。只能用 drop()方法删除整个Collection。
默认基于 Insert 的次序排序的。如果查询时没有排序,则总是按照insert的顺序返回。
FIFO。如果超过了Collection的限定大小,则用 FIFO 算法,新记录将替代最先 insert的记录。