mongodb性能分析和优化之explain和hint

 

一:explain演示

1. 构建数据

  为了方便演示,我需要create ten dayyy to inventory,而且还是要在no index 的情况下,比如下面这样:

 1 db.inventory.insertMany([
 2 { "_id" : 1, "item" : "f1", type: "food", quantity: 500 },
 3 { "_id" : 2, "item" : "f2", type: "food", quantity: 100 },
 4 { "_id" : 3, "item" : "p1", type: "paper", quantity: 200 },
 5 { "_id" : 4, "item" : "p2", type: "paper", quantity: 150 },
 6 { "_id" : 5, "item" : "f3", type: "food", quantity: 300 },
 7 { "_id" : 6, "item" : "t1", type: "toys", quantity: 500 },
 8 { "_id" : 7, "item" : "a1", type: "apparel", quantity: 250 },
 9 { "_id" : 8, "item" : "a2", type: "apparel", quantity: 400 },
10 { "_id" : 9, "item" : "t2", type: "toys", quantity: 50 },
11 { "_id" : 10, "item" : "f4", type: "food", quantity: 75 }]);

2. 无索引查询

 b.inventory.find(
   { quantity: { $gte: 100, $lte: 200 } }
).explain("executionStats")

  从上图中,我们看到了三个圈圈,这些都是我们在find中非常重要的信息,具体信息解释如下:

<1>COLLSCAN

  这个是什么意思呢? 如果你仔细一看,应该知道就是CollectionScan,就是所谓的“集合扫描”,对不对,看到集合扫描是不是就可以直接map到 数据库中的yyyble scan/heap scan呢??? 是的,这个就是所谓的性能最烂最无奈的由来。

<2> nReturned

  这个很简单,就是所谓的numReturned,就是说最后返回的num个数,从图中可以看到,就是最终返回了三条。。。

<3> docsExamined

  那这个是什么意思呢??就是documentsExamined,检查了10个documents。。。而从返回上面的nReturned。。。

  ok,那从上面三个信息中,我们可以得出,原来我examine 10 条数据,最终才返回3条,说明做了7条数据scan的无用功,那么这个时候问题就来了,如何减少examine的documents。。。


3. 使用single field 加速查找

  知道前因后果之后,我们就可以进行针对性的建立索引,比如在quality字段之上,如下:

 db.inventory.createIndex({ quantity: 1})

db.inventory.find(
   { quantity: { $gte: 100, $lte: 200 } }
).explain("executionStats")

  好了,这时候就有意思了,当我们执行完createindex之后,再次explain,4个重要的parameters就漂下来了:

<1> IXSCAN

  这个时候再也不是所谓的COLLSCAN了,而是IndexScan,这就说明我们已经命中索引了。

<2> nReturned,totalDocsExamined,totalKeysExamined

  从图中可以看到三个参数都是3,这就说明我们的mongodb查看了3个key,3个document,返回3个文档,这个就是所谓的高性能所在,对吧。

二:hint演示

  说到hint,我想大家也是知道的,很好玩的一个东西,就是用来force mongodb to excute special index,对吧,为了方便演示,我们做两组复合索 引,比如这次我们在quality和type上构建一下:

  building完成之后,我们故意这一个这样的查询,针对quantity是一个范围,而type是一个定值的情况下,我们force mongodb去使用quantity开头 的复合索引,从而强制mongodb give up 那个以{type:1,quantity:1}的复合索引,很有意思哦,比如下图:

  从图中,可以看到,我们检查了6个keys,而从最终找到了2个文档,现在我们就知道了,2和6之间还是有不足的地方等待我们去优化了,对吧,下面 我们不hint来看一下mongodb的最优的plan是怎么样的。

  除此之外,也可以强迫查询不适用索引,做表扫描:

db.users.find().hint({"$natural":1})

关于文件系统

  MongoDB在WiredTiger存储引擎下建议使用XFS文件系统。Ext4最为常见,但是由于ext文件系统的内部journal和WiredTiger有所冲突,所以在IO压力较大情况下表现不佳。

透明大页

  Transparent Huge Pages (THP) 是Linux的一种内存管理优化手段,通过使用更大的内存页来减少Translation Lookaside Buffer(TLB)的额外开销。 MongoDB数据库大部分是比较分散的小量数据读写,THP对MongoDB这种工况会有负面的影响所以建议关闭。

http://docs.mongoing.com/manual-zh/tutorial/transparent-huge-pages.html

对慢查询日志进行监控

  默认情况下MongoDB会在日志文件中(mongod.log)记录超过100ms的数据库操作。如下:

 

2020-04-03T16:53:07.985+0800 I WRITE    [conn44] update fff.systemErrorLog query: { _id: "-1" } planSummary: IDHACK update: { _class: "com.xxx.yyy.base.model.SystemErrorLog", _id: "
-1", errorCode: "-1", errorInfo: "系统参数hsta.serverMode不存在,请在application.properties中设置,具体参见BaseConfigConst说明!", stackTrace: "	at com.xxx.yyy.base.BaseConfig.getConfig(Bas
eConfig.java:105)
	at com.xxx.yyy.base.BaseConfig.getConfig(BaseConfig.java:110)
	at org.apache.iba..." } keysExamined:1 docsExamined:1 nMatched:1 nModified:1 writeConflicts:32 numYields:32 locks:{ Global: { acquireCount: { r: 33, w: 33 } }, Dayyybase: { acquireCoun
t: { w: 33 } }, Collection: { acquireCount: { w: 33 } } } 138ms
2020-04-03T16:53:07.985+0800 I COMMAND  [conn44] command fff.$cmd command: update { update: "systemErrorLog", ordered: false, updates: [ { q: { _id: "-1" }, u: { _class: "com.xxx.t
a.base.model.SystemErrorLog", _id: "-1", errorCode: "-1", errorInfo: "系统参数hsta.serverMode不存在,请在application.properties中设置,具体参见BaseConfigConst说明!", stackTrace: "	at com.
xxx.yyy.base.BaseConfig.getConfig(BaseConfig.java:105)
	at com.xxx.yyy.base.BaseConfig.getConfig(BaseConfig.java:110)
	at org.apache.iba..." }, upsert: true } ] } numYields:0 reslen:59 locks:{ Global: { acquireCount: { r: 33, w: 33 } }, Dayyybase: { acquireCount: { w: 33 } }, Collection: { acquireCount
: { w: 33 } } } protocol:op_query 221ms
2020-04-03T16:53:07.985+0800 I WRITE    [conn40] update fff.systemErrorLog query: { _id: "-1" } planSummary: IDHACK update: { _class: "com.xxx.yyy.base.model.SystemErrorLog", _id: "
-1", errorCode: "-1", errorInfo: "系统参数hsta.serverMode不存在,请在application.properties中设置,具体参见BaseConfigConst说明!", stackTrace: "	at com.xxx.yyy.base.BaseConfig.getConfig(Bas
eConfig.java:105)
	at com.xxx.yyy.base.BaseConfig.getConfig(BaseConfig.java:110)
	at org.apache.iba..." } keysExamined:1 docsExamined:1 nMatched:1 nModified:1 writeConflicts:32 numYields:32 locks:{ Global: { acquireCount: { r: 33, w: 33 } }, Dayyybase: { acquireCoun
t: { w: 33 } }, Collection: { acquireCount: { w: 33 } } } 130ms
2020-04-03T16:53:07.985+0800 I COMMAND  [conn40] command fff.$cmd command: update { update: "systemErrorLog", ordered: false, updates: [ { q: { _id: "-1" }, u: { _class: "com.xxx.t
a.base.model.SystemErrorLog", _id: "-1", errorCode: "-1", errorInfo: "系统参数hsta.serverMode不存在,请在application.properties中设置,具体参见BaseConfigConst说明!", stackTrace: "	at com.
xxx.yyy.base.BaseConfig.getConfig(BaseConfig.java:105)
	at com.xxx.yyy.base.BaseConfig.getConfig(BaseConfig.java:110)
	at org.apache.iba..." }, upsert: true } ] } numYields:0 reslen:59 locks:{ Global: { acquireCount: { r: 33, w: 33 } }, Dayyybase: { acquireCount: { w: 33 } }, Collection: { acquireCount
: { w: 33 } } } protocol:op_query 214ms
2020-04-03T16:53:07.985+0800 I WRITE    [conn38] update fff.systemErrorLog query: { _id: "-1" } planSummary: IDHACK update: { _class: "com.xxx.yyy.base.model.SystemErrorLog", _id: "
-1", errorCode: "-1", errorInfo: "系统参数hsta.serverMode不存在,请在application.properties中设置,具体参见BaseConfigConst说明!", stackTrace: "	at com.xxx.yyy.base.BaseConfig.getConfig(Bas
eConfig.java:105)
	at com.xxx.yyy.base.BaseConfig.getConfig(BaseConfig.java:110)
	at org.apache.iba..." } keysExamined:1 docsExamined:1 nMatched:1 nModified:1 writeConflicts:32 numYields:32 locks:{ Global: { acquireCount: { r: 33, w: 33 } }, Dayyybase: { acquireCoun
t: { w: 33 } }, Collection: { acquireCount: { w: 33 } } } 128ms

 

mongostat、mongotop

  有时候,单个语句并不慢,但是整体应用慢,此时可以通过mongostat或mongotop监控整体负载情况。他们是mongodb自带的工具,和jdk的工具类似。这两个命令对于我们处理MongoDB数据库变慢等等问题非常有用,能详细的统计MongoDB当前的状态信息。除此之外,还可以用db.serverStatus()、db.Stats()、开启profile功能通过查看日志进行监控分析。

  例如,查看整体当前的负载使用mongostat,如下:

[root@hs-10-20-37-72 bin]# ./mongostat 
insert query update delete getmore command dirty used flushes vsize  res qrw arw net_in net_out conn                time
    *0    *0     *0     *0       0     3|0  0.0% 0.1%       0 1.19G 100M 0|0 0|0   245b   51.8k   11 Apr 10 22:13:44.775
    *0    *0     *0     *0       0     1|0  0.0% 0.1%       0 1.19G 100M 0|0 0|0   157b   45.3k   11 Apr 10 22:13:45.776
    *0    *0     *0     *0       0     1|0  0.0% 0.1%       0 1.19G 100M 0|0 0|0   157b   45.3k   11 Apr 10 22:13:46.777
    *0    *0     *0     *0       0     1|0  0.0% 0.1%       0 1.19G 100M 0|0 0|0   157b   45.2k   11 Apr 10 22:13:47.779
    *0    *0     *0     *0       0     3|0  0.0% 0.1%       0 1.19G 100M 0|0 0|0   216b   45.7k   11 Apr 10 22:13:48.775
    *0    *0     *0     *0       0     1|0  0.0% 0.1%       0 1.19G 100M 0|0 0|0   157b   45.3k   11 Apr 10 22:13:49.776
    *0    *0     *0     *0       0     1|0  0.0% 0.1%       0 1.19G 100M 0|0 0|0   157b   45.3k   11 Apr 10 22:13:50.776

  每列的含义可通过./bin/mongostat --help查看。

  mongotop可以查看每个集合的负载情况,如下:

[root@hs-10-20-37-72 bin]# ./mongotop 
2020-04-10T22:15:21.220+0800    connected to: 127.0.0.1

                                ns    total    read    write    2020-04-10T22:15:22+08:00
                admin.system.roles      0ms     0ms      0ms                             
                admin.system.users      0ms     0ms      0ms                             
              admin.system.version      0ms     0ms      0ms                             
                 local.startup_log      0ms     0ms      0ms                             
              local.system.replset      0ms     0ms      0ms                             
        xx.processTemplate      0ms     0ms      0ms                             
         xx.systemErrorLog      0ms     0ms      0ms                             
              xx.systemLog      0ms     0ms      0ms                             
xx.system_log_field_deyyyil      0ms     0ms      0ms                             

                                ns    total    read    write    2020-04-10T22:15:23+08:00
                admin.system.roles      0ms     0ms      0ms                             
                admin.system.users      0ms     0ms      0ms                             
              admin.system.version      0ms     0ms      0ms                             
                 local.startup_log      0ms     0ms      0ms                             
              local.system.replset      0ms     0ms      0ms                             
        xx.processTemplate      0ms     0ms      0ms                             
         xx.systemErrorLog      0ms     0ms      0ms                             
              xx.systemLog      0ms     0ms      0ms                             
xx.system_log_field_deyyyil      0ms     0ms      0ms                             

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

正确使用写关注设置(Write Concern)

  MongoDB的建议最小部署是一个复制集,包含3个数据节点。默认情况下应用的写操作(更新,插入或者删除)在主节点上完成后就会立即返回。写操作则通过OPLOG方式在后台异步方式复制到其他节点。在极端情况下,这些写操作可能还未在复制到从节点的时候主节点就出现宕机。这个时候发生主备节点切换,原主节点的写操作会被回滚到文件而对应用不可见。为防止这种情况出现,MongoDB建议对重要的数据使用 {w: “marjority”} 的选项。{w: “majority”} 可以保证数据在复制到多数节点后才返回成功结果。使用该机制可以有效防止数据回滚的发生。

  另外你可以使用 {j:1} (可以和 w:”majrotiy” 结合使用) 来指定数据必须在写入WAL日志之后才向应用返回成功确认。这个会导致写入性能有所下降,但是对于重要的数据可以考虑使用。

正确使用读选项设置(Read Preference)

  MongoDB由于是一个分布式系统,一份数据会在多个节点上进行复制。从哪个节点上读数据,要根据应用读数据的需求而定。以下是集中可以配置的读选项:

  • primary: 默认,在主节点上读数据

  • priaryPreferred: 先从主节点上读,如果为成功再到任意一台从节点上读

  • secondary: 在从节点上读数据(当有多台节点的时候,随机的使用某一台从节点)

  • secondaryPreferred: 首先从从节点上读,如果从节点由于某种原因不能提供服务,则从主节点上进行读

  • nearest: 从距离最近的节点来读。距离由ping操作的时间来决定。

  除第一个选项之外,其他读选项都存在读到的数据不是最新的可能。原因是数据的复制是后台异步完成的。

不要实例化多个MongoClient

  MongoClient是个线程安全的类,自带线程池。通常在一个JVM内不要实例化多个MongoClient实例,避免连接数过多和资源的不必要浪费。

对写操作使用Retry机制

  MongoDB使用复制集技术可以实现99.999%的高可用。当一台主节点不能写入时,系统会自动故障转移到另一台节点。转移可能会耗时几秒钟,在这期间应用应该捕获相应的Exception并执行重试操作。重试应该有backoff机制,例如,分别在1s,2s,4s,8s等时候进行重试。

建索引要在后台运行

  在对一个集合创建索引时,该集合所在的数据库将不接受其他读写操作。对数据量的集合建索引,建议使用后台运行选项 {background: true}

预读值(readahead)设置

  预读值是文件操作系统的一个优化手段,大致就是在程序请求读取一个页面的时候,文件系统会同时读取下面的几个页面并返回。这原因是因为很多时候IO最费时的磁盘寻道。通过预读,系统可以提前把紧接着的数据同时返回。假设程序是在做一个连续读的操作,那么这样可以节省很多磁盘寻道时间。

  MongoDB很多时候会做随机访问。对于随机访问,这个预读值应该设置的较小为好.一般来说32是一个不错的选择。

  你可以使用下述命令来显示当前系统的预读值:

sudo blockdev --report

要更改预读值,可以用以下命令:

sudo blockdev --setra 32

把 换成合适的存储设备。

 

分配足够的Oplog空间

  足够的Oplog空间可以保证有足够的时间让你从头恢复一个从节点,或者对从节点执行一些比较耗时的维护操作。假设你最长的下线维护操作需要H小时,那么你的Oplog 一般至少要保证可以保存 H 2 或者 H3 小时的oplog。

  如果你的MongoDB部署的时候未设置正确的Oplog 大小,可以参照下述链接来调整:

  http://docs.mongoing.com/manual-zh/tutorial/change-oplog-size.html

是否适合分页查询?分片情况下如何?

文档大小

  MongoDB中BSON文档最大支持16M,用户应该避免一些特定的应用程序无限制的使文档增大。例如,电商平台应用中,很难估算每个商品可能会收到多少用户评价,所以,通常的做法是只显示一部分商品评价,比如最近的以及最广泛的评价。相比把商品和评价放在一个文档中,把每个评价或者一组评价单独放在一个文档中会更方便,同时,在存放商品的文档中存放一些主要的评价以便快速访问即可。

GridFS

  大于16M的文件,MongoDB提供了GridFS这种功能,所有的驱动程序都支持这种功能。GridFS可以自动地把大的数据分成多个块,每个块256KB,同时维护这些块的元数据。GridFS能检索单个块也能检索整个文档。比如,应用可以快速的跳转到一段视频的具体某个时间戳的位置。GridFS通常用来存储照片以及视频这些比较大的二进制文件。

Capped Collections

  Capped Collections是固定大小的集合,支持高流量的数据读写。Capped Collections类似于循环缓冲区:数据按照插入的顺序依次插入文档中,当集合的大小达到设定的阈值后,最先插入的数据会被删除掉,为最新插入的数据腾出空间。例如,把日志信息存储在一个高容量的Capped Collections集合中,就可以快速的检索到最新的日志信息。

https://www.cnblogs.com/Tsoagyyy/p/8877404.html

Mongodb count不准原因分析

https://yq.aliyun.com/articles/704434

Mongodb的性能优化可参考https://www.mongodb.com/blog/post/performance-best-practices-benchmarking。

posted @ 2019-05-01 20:53  zhjh256  阅读(758)  评论(0编辑  收藏  举报