MongoDB 性能调优

一、explain 执行计划

MongoDB 提供了一个 explain 命令让我们获知系统如何处理查询请求。利用 explain 命令,我们可以很好地观察系统如何使用索引来加快检索,同时可以针对性优化索引。

> db.t5.ensureIndex({name:1}) 
> db.t5.ensureIndex({age:1}) 
> db.t5.find({age:{$gt:45}}, {name:1}).explain() 
{ 
     "cursor" : "BtreeCursor age_1", 
     "nscanned" : 0, 
     "nscannedObjects" : 0, 
     "n" : 0, 
     "millis" : 0, 
     "nYields" : 0, 
     "nChunkSkips" : 0, 
     "isMultiKey" : false, 
     "indexOnly" : false, 
     "indexBounds" : { 
         "age" : [ 
             [ 
                 45, 
                 1.7976931348623157e+308 
             ] 
         ]
    } 
}

字段说明:

  • cursor:返回游标类型(BasicCursor 或 BtreeCursor) ;
  • nscanned:被扫描的文档数量;
  • n:返回的文档数量;
  • millis:耗时(毫秒) ;
  • indexBounds:所使用的索引;

二、优化器 profile 

在 MySQL 中,慢查询日志是经常作为我们优化数据库的依据,那在 MongoDB 中是否有类似的功能呢?

答案是肯定的,那就是 MongoDB Database Profiler。所以 MongoDB 不仅有,而且还有一些比 MySQL 的 Slow Query Log 更详细的信息。

1、开启 Profiling 功能

有两种方式可以控制 Profiling 的开关和级别,第一种是直接在启动参数里直接进行设置。启动 MongoDB 时加上–profile=级别 即可。

也可以在客户端调用 db.setProfilingLevel(级别) 命令来实时配置,Profiler 信息保存在system.profile 中。

我们可以通过 db.getProfilingLevel()命令来获取当前的 Profile 级别,类似如下操作:

> db.setProfilingLevel(2); 
{ "was" : 0, "slowms" : 100, "ok" : 1 }

上面 profile 的级别可以取 0,1,2 三个值,他们表示的意义如下:

  • 0 – 不开启;
  • 1 – 记录慢命令 (默认为>100ms); 
  • 2 – 记录所有命令;

Profile 记录在级别 1 时会记录慢命令,那么这个慢的定义是什么?上面我们说到其默认为100ms,当然有默认就有设置,其设置方法和级别一样有两种,一种是通过添加–slowms 启动参数配置。第二种是调用 db.setProfilingLevel 时加上第二个参数:

db.setProfilingLevel( level , slowms ) 
db.setProfilingLevel( 1 , 10 );

2、查询 Profiling 记录

与 MySQL 的慢查询日志不同,MongoDB Profile 记录是直接存在系统 db 里的,记录位置
system.profile ,所以,我们只要查询这个 Collection 的记录就可以获取到我们的 Profile 记录了。

列出执行时间长于某一限度(5ms)的 Profile 记录:

db.system.profile.find( { millis : { $gt : 5 } } )

查看最新的 Profile 记录:

db.system.profile.find().sort({$natural:-1}).limit(1)
> db.system.profile.find().sort({$natural:-1}).limit(1)
{ "ts" : ISODate("2018-05-20T16:50:36.321Z"), "info" : "query test.system.profile reslen:1219 
nscanned:8 \nquery: { query: {}, orderby: { $natural: -1.0 } } nreturned:8 bytes:1203", "millis" : 
0 } 
>

字段说明:

  • ts: 该命令在何时执行;
  • info: 本命令的详细信息;
  • reslen: 返回结果集的大小;
  • nscanned: 本次查询扫描的记录数;
  • nreturned: 本次查询实际返回的结果集;
  • millis: 该命令执行耗时,以毫秒记;

MongoDB Shell 还提供了一个比较简洁的命令 show profile,可列出最近 5 条执行时间超过1ms 的 Profile 记录。

三、性能优化案例

如果 nscanned(扫描的记录数)远大于 nreturned(返回结果的记录数)的话,那么我们就要考虑通过加索引来优化记录定位了。reslen 如果过大,那么说明我们返回的结果集太大了,这时请查看 find 函数的第二个参数是否只写上了你需要的属性名。

对于创建索引的建议是:如果很少读,那么尽量不要添加索引,因为索引越多,写操作会越慢。如果读量很大,那么创建索引还是比较划算的。

假设我们按照时间戳查询最近发表的 10 篇博客文章:

articles = db.posts.find().sort({ts:-1}); 

for (var i=0; i< 10; i++) { 
     print(articles[i].getSummary()); 
}

1、优化方案1 :创建索引

在查询条件的字段上,或者排序条件的字段上创建索引,可以显著提高执行效率:

db.posts.ensureIndex({ts:1});

2、优化方案 2:限定返回结果条数

使用 limit()限定返回结果集的大小,可以减少 database server 的资源消耗,可以减少网络传输数据量。

articles = db.posts.find().sort({ts:-1}).limit(10);

3、优化方案 3: 只查询使用到的字段,而不查询所有字段

在本例中,博客日志记录内容可能非常大,而且还包括了评论内容(作为 embeded 文档)。

所以只查询使用的字段,比查询所有字段效率更高:

articles = db.posts.find({}, {ts:1,title:1,author:1,abstract:1}).sort({ts:-1}).limit(10);

注意:如果只查询部分字段的话,不能用返回的对象直接更新数据库。

下面的代码是错误的:

a_post = db.posts.findOne({}, Post.summaryFields); 
a_post.x = 3; 
db.posts.save(a_post);

4、优化方案 4:采用 capped collection 

capped Collections 比普通 Collections 的读写效率高。Capped Collections 是高效率的 Collection
类型,它有如下特点:

  • 固定大小;Capped Collections 必须事先创建,并设置大小:
db.createCollection("mycoll", {capped:true, size:100000})
  • Capped Collections 可以 insert 和 update 操作;不能 delete 操作。只能用 drop()方法删除整个 Collection。
  • 默认基于 Insert 的次序排序的。如果查询时没有排序,则总是按照 insert 的顺序返回。
  • FIFO:如果超过了 Collection 的限定大小,则用 FIFO 算法,新记录将替代最先 insert 的记录。

5、优化方案 5:采用 Server Side Code Execution 

Server-Side Processing 类似于 SQL 数据库的存储过程,使用 Server-Side Processing 可以减小网络通讯的开销。

6、优化方案 6:Hint 

一般情况下 MongoDB query optimizer 都工作良好,但有些情况下使用 hint()可以提高操作效率。

Hint 可以强制要求查询操作使用某个索引。例如,如果要查询多个字段的值,如果在其中一个字段上有索引,可以使用 hint:

db.collection.find({user:u, foo:d}).hint({user:1});

7、优化方案 7:采用 Profiling 

Profiling 功能肯定是会影响效率的,但是不太严重,原因是他使用的是 system.profile 来记录,而 system.profile 是一个 capped collection 这种 collection 在操作上有一些限制和特点,但是效率更高。

四、性能监控

1、mongosniff 

此工具可以从底层监控到底有哪些命令发送给了 MongoDB 去执行,从中就可以进行分析。

以 root 身份执行:

./mongosniff --source NET lo

然后其会监控位到本地以 localhost 监听默认 27017 端口的 MongoDB 的所有包请求,如执行”show dbs” 操作。

[root@localhost bin]# ./mongo 
MongoDB shell version: 1.8.1 
connecting to: test 
> show dbs 
admin 0.0625GB 
foo 0.0625GB 
local (empty) 
test 0.0625GB 
>

那么你可以看到如下输出:

[root@localhost bin]# ./mongosniff --source NET lo
sniffing... 27017 
127.0.0.1:38500 -->> 127.0.0.1:27017 admin.$cmd 60 bytes id:537ebe0f 1400815119 
 query: { whatsmyuri: 1 } ntoreturn: 1 ntoskip: 0 
127.0.0.1:27017 <<-- 127.0.0.1:38500 78 bytes id:531c3855 1394358357 - 1400815119
 reply n:1 cursorId: 0 
 { you: "127.0.0.1:38500", ok: 1.0 } 
127.0.0.1:38500 -->> 127.0.0.1:27017 admin.$cmd 80 bytes id:537ebe10 1400815120 
 query: { replSetGetStatus: 1, forShell: 1 } ntoreturn: 1 ntoskip: 0 
127.0.0.1:27017 <<-- 127.0.0.1:38500 92 bytes id:531c3856 1394358358 - 1400815120
 reply n:1 cursorId: 0 
 { errmsg: "not running with --replSet", ok: 0.0 } 
127.0.0.1:38500 -->> 127.0.0.1:27017 admin.$cmd 67 bytes id:537ebe11 1400815121 
 query: { listDatabases: 1.0 } ntoreturn: -1 ntoskip: 0 
127.0.0.1:27017 <<-- 127.0.0.1:38500 293 bytes id:531c3857 1394358359 - 1400815121
 reply n:1 cursorId: 0 
 { databases: [ { name: "foo", sizeOnDisk: 67108864.0, empty: false }, { name: "test", 
sizeOnDisk: 67108864.0, empty: false }, { name: "admin", sizeOnDisk: 67108864.0, empty: false }, 
{ name: "local", sizeOnDisk: 1.0, empty: true } ], totalSize: 201326592.0, ok: 1.0 } 
127.0.0.1:38500 -->> 127.0.0.1:27017 admin.$cmd 80 bytes id:537ebe12 1400815122 
query: { replSetGetStatus: 1, forShell: 1 } ntoreturn: 1 ntoskip: 0 
127.0.0.1:27017 <<-- 127.0.0.1:38500 92 bytes id:531c3858 1394358360 - 1400815122
 reply n:1 cursorId: 0 
 { errmsg: "not running with --replSet", ok: 0.0 }

如果将这些输出到一个日志文件中,那么就可以保留下所有数据库操作的历史记录,对于后期的性能分析和安全审计等工作将是一个巨大的贡献。

2、Mongostat 

此工具可以快速的查看某组运行中的 MongoDB 实例的统计信息,用法如下:

[root@localhost bin]# ./mongostat 

下面是执行结果(部分):

[root@localhost bin]# ./mongostat 
insert query update delete ...... locked % idx miss % qr|qw ar|aw conn time 
 *0 *0 *0 *0 ...... 0 0 0|0 1|0 4 01:19:15 
 *0 *0 *0 *0 ...... 0 0 0|0 1|0 4 01:19:16 
 *0 *0 *0 *0 ...... 0 0 0|0 1|0 4 01:19:17

字段说明:

  • insert: 每秒插入量;
  • query: 每秒查询量;
  • update: 每秒更新量;
  • delete: 每秒删除量;
  • locked: 锁定量;
  • qr | qw: 客户端查询排队长度(读|写); 
  • ar | aw: 活跃客户端量(读|写) ;
  • conn: 连接数;
  • time: 当前时间;

它每秒钟刷新一次状态值,提供良好的可读性,通过这些参数可以观察到一个整体的性能情况。

3、db.serverStatus 

这个命令是最常用也是最基础的查看实例运行状态的命令之一,下面我们看一下它的输出:

> db.serverStatus() 
{ 
 "host" : "localhost.localdomain", 
 "version" : "1.8.1", --服务器版本
 "process" : "mongod", 
 "uptime" : 3184, --启动时间(秒) 
 "uptimeEstimate" : 3174, 
 "localTime" : ISODate("2018-05-28T11:20:22.819Z"),
 "globalLock" : { 
 "totalTime" : 3183918151, 
 "lockTime" : 10979, 
 "ratio" : 0.000003448267034299149, 
 "currentQueue" : { 
 "total" : 0, --当前全部队列量
 "readers" : 0, --读请求队列量
 "writers" : 0 --写请求队列量
 }, 
 "activeClients" : { 
 "total" : 0, --当前全部客户端连接量
 "readers" : 0, --客户端读请求量
 "writers" : 0 --客户端写请求量
 } 
 }, 
 "mem" : { 
 "bits" : 32, --32 位系统
 "resident" : 20, --占用物量内存量
 "virtual" : 126, --虚拟内存量
 "supported" : true, --是否支持扩展内存
 "mapped" : 32 
 }, 
 "connections" : { 
 "current" : 1, --当前活动连接量
 "available" : 818 --剩余空闲连接量
 }, 
…… 
 "indexCounters" : { 
 "btree" : { 
 "accesses" : 0, --索引被访问量
 "hits" : 0, --索引命中量
 "misses" : 0, --索引偏差量
 "resets" : 0, 
 "missRatio" : 0 --索引偏差率(未命中率) 
 } 
 }, 
…… 
 "network" : { 
 "bytesIn" : 1953, --发给此服务器的数据量(单位:byte) 
 "bytesOut" : 25744, --此服务器发出的数据量(单位:byte) 
 "numRequests" : 30 --发给此服务器的请求量
 }, 
 "opcounters" : { 
 "insert" : 0, --插入操作的量
 "query" : 1, --查询操作的量
 "update" : 0, --更新操作的量
 "delete" : 0, --删除操作的量
 "getmore" : 0, 
 "command" : 31 --其它操作的量
 }, 
…… 
 "ok" : 1 
} 
>

此工具提了比较详细的信息,以上已经对主要的一些参数做了说明,请大家参考。

4、db.stats 

db.stats 查看数据库状态信息。使用样例如下:

> db.stats()
{ 
 "db" : "test", 
 "collections" : 7, --collection 数量
 "objects" : 28, --对象数量
 "avgObjSize" : 50.57142857142857, --对象平均大小
 "dataSize" : 1416, --数据大小
 "storageSize" : 31744, --数据大小(含预分配空间) 
 "numExtents" : 7, --事件数量
 "indexes" : 7, --索引数量
 "indexSize" : 57344, --索引大小
 "fileSize" : 50331648, --文件大小
 "ok" : 1 --本次取 stats 是否正常
} 
>

通过这个工具,可以查看所在数据库的基本信息。

5、第三方工具

MongoDB 从一面世就得到众多开源爱好者和团队的重视,在常用的监控框架如 cacti、Nagios、Zabbix 等基础上进行扩展,进行 MongoDB 的监控都是非常方便的,有兴趣的朋友可以自已试试。

猜你喜欢

转载自blog.csdn.net/qq_35029061/article/details/128638799