说到数据库方面的优化,我们首先想到的是利用索引技术,mongoDB作为数据库的一种,利用索引来提高查询效率同样适用。下面就我在mongoDB的日常工作中经常使用到的性能优化谈谈自己的理解。
一:利用explain执行计划来查看系统执行过程
MongoDB本身提供了一个explain()命令来获取系统执行查询的处理过程,如下图:
文章图片
重要字段说明如下:
* cursor:返回的游标类型,一般分为BasicCursor(系统默认游标)和BtreeCursor(基于树的游标)。
*nscanned:被扫描的文档数量。
*n:返回的文档数量。
*millis:执行查询所消耗的时间,单位为毫秒。
*indexOnly:是否使用索引。
*indexBound:所使用的索引列表。
下面我们建立索引,来看看explain的执行过程:
文章图片
上图中可以看出,建立索引后游标类型变成了基于树状的Btree结构游标,搜索速度快,从而大大提高了执行查询效率。
索引可以提高查询性能,但有一点需要注意的是,索引并不是越多越好,如果建立过多,系统性能反而会下降。因为每当你建立一个索引时,系统都会建立一个索引表,用于检索指定的列,下次当你对已建立的索引列进行插入或者修改时,数据库都会对原来的索引表进行重新排序,在这过程中会非常消耗性能。
二:优化器Profiler解析
Mongodb本身带有一种profiler机制,可以方便地记录所有耗时的操作,以便于调优。
开启profiler功能有两种方法:
第一种就是直接在启动参数里面进行设置,就在启动mongodb时候添加-profile=“级别“:
mongod --profile=1 --slowms=15
第二种就是在客户端执行“db.setProfilingLevel(级别,slowms)”命令:
db.setProfilingLevel(0,20)
这里的“级别”分为:0(不开启),1(记录慢命令),2(记录所有命令)。slowms参数代表慢操作记录的开始时间,默认为100ms。
需要注意的是,慢操作记录的开始时间slowms会应用到整个mongod实例中,当你改变了记录开始时间,你会改变整个数据库实例状态。这一点非常重要,官网中有特别指出:
文章图片
当你开启优化器profiler时,系统会自动在数据库中创建一个system.profile集合,这个集合会默认记录所有的慢操作日志。
查询集合结果如下:
>db.system.profile.find()
{ "ts" : ISODate("2016-06-16T19:45:10.359Z"), "info" : "query test.system.profile reslen:36 nscanned:0\nquery: {}nreturned:0 bytes:20", "millis" : 0 }
{ "ts" : ISODate("2016-06-16T19:45:13.562Z"), "info" : "query test.$cmd ntoreturn:1 command: { count: \"system.profile\", query: {}, fields: {} } reslen:64 bytes:48", "millis" : 0 }
{ "ts" : ISODate("2016-06-16T19:45:13.562Z"), "info" : "query test.system.profile ntoreturn:5 reslen:36 nscanned:2\nquery: { query: { millis: { $gt: 0.0 } }, orderby: { $natural: -1.0 } }nreturned:0 bytes:20", "millis" : 0 }
{ "ts" : ISODate("2016-06-16T19:45:17.171Z"), "info" : "query test.emp reslen:261 nscanned:5\nquery: {}nreturned:5 bytes:245", "millis" : 0 }
主要参数说明如下:
ts:该命令执行的时间。
info:本命令的详细信息。
reslen:返回结果集的大小。
nscanned:本次查询扫描的记录数。
nretured:本次查询时间返回的结果集。
millis:该命令执行的耗时。单位为毫秒
profile分析数据查询示例:
//1.执行时间大于5毫秒的操作信息:
>db.system.profile.find( { millis : { $gt : 5 } } ).pretty()//2.某一时间段的操作执行信息:
>db.system.profile.find(
{
ts : {
$gt : new ISODate("2016-06-17T03:00:00Z") ,
$lt : new ISODate("2016-06-17T03:40:00Z")
}
}
).pretty()//3.在某一时间段,某一用户执行信息并对每一次操作执行所消耗的时间进行排序:
>db.system.profile.find(
{
ts : {
$gt : new ISODate("2016-06-17T03:00:00Z") ,
$lt : new ISODate("2016-06-17T03:40:00Z")
}
},
{ user : 0 }
).sort( { millis : -1 } )
当开启优化器profiler,其实只会对系统性能产生较小的效果。system.profile是一个默认大小为1M的capped collection集合,这样大小的集合通常可以存储数千的profile文档数据,但是有一些应用程序每执行一次操作,可能会产生更多或者更少的分析数据。
如何改变system.profile集合的大小?
例如,你需要创建一个400000bytes的system.profile集合,在mongo脚本执行以下命令:
//1.关闭优化器
>db.setProfilingLevel(0)//2.删除system.profile
>db.system.profile.drop()//3.创建一个新的system.profile集合
>db.createCollection( "system.profile", { capped: true, size:4000000 } )//4.重启优化器
>db.setProfilingLevel(1)
三:性能优化总结
【MongoDB的性能优化】 方法1:采用Index索引技术。如前面所述,索引并不是越多越好,那什么情况下建立索引最好呢?当你数据库的总记录数远大于你需要返回结果的记录,并且读请求较多的时候,创建索引是一个非常好的选择。
方法2:只查询你需要的部分字段,不要查询所有字段。这种方法很好理解。比如说一个银行的客户信息存储非常大,但我们只需要查询出所有客户姓名字段即可,这样比在数据库调取出客户所有信息显然快很多。
方法3:利用优化器Profiler。如前面所述,开启profile功能会影响系统效率的,但影响效果较小。因为它采用的是system.profile集合来记录,这个集合是一个Capped Collction,存储查询效率高。
方法4:直接使用Capped Collction。这个集合其实是一个具有固定大小,默认基于insert次序排序,并且采用FIFO算法的集合(详细解释会在后面文章中指出),读写效率比普通的Collection高。
方法5:采用Server Side Code Exeution。这个方法就非常类似于sql数据库中存储过程,将一些常用的sql功能进行封装,需要时直接调用,会大大减少网络传输的开销。
另外如使用hint强制使用索引,以及限制limit返回的结果条数这些,都是可选的优化方法。