zoukankan      html  css  js  c++  java
  • MongoDB使用优化

    一、监控

    mongodb可以通过profile来监控数据,进行优化。

    查看当前是否开启profile功能用命令:db.getProfilingLevel()返回level等级,值为0|1|2,分别代表意思:0代表关闭,1代表记录慢命令,2代表全部。

    开始profile功能为db.setProfilingLevel(level);

    level为1的时候,慢命令默认值为100ms,更改为db.setProfilingLevel(level,slowms)如db.setProfilingLevel(1,50)这样就更改为50毫秒

    通过db.system.profile.find() 查看当前的监控日志。

    通过执行db.system.profile.find({millis:{$gt:500}})能够返回查询时间在500毫秒以上的查询命令。

    这里值的含义是

    ts:命令执行时间
    info:命令的内容
    query:代表查询
    order.order: 代表查询的库与集合
    reslen:返回的结果集大小,byte数
    nscanned:扫描记录数量
    nquery:后面是查询条件
    nreturned:返回记录数及用时
    millis:所花时间

    如果发现时间比较长,那么就需要作优化。

    比如nscanned数很大,或者接近记录总数,那么可能没有用到索引查询。

    reslen很大,有可能返回没必要的字段。

    nreturned很大,那么有可能查询的时候没有加限制。

    mongo可以通过db.serverStatus()查看mongod的运行状态

    二、索引

    首选就是为待查询的字段建立索引,不过需要特别注意的是,索引不是万能灵药。如果需要查询超过一半的集合数据,索引还不如直接遍历来的好。

    索引的原理是通过建立指定字段的B树,通过搜索B树来查找对应document的地址。这也就解释了如果需要查询超过一半的集合数据,直接遍历省去了搜索B树的过程,效率反而会高。

    关于索引,索引列颗粒越小越好,什么叫颗粒越小越好?在索引列中每个数据的重复数量称为颗粒,也叫作索引的基数。如果数据的颗粒过大,索引就无法发挥该有的性能。例如,我们拥有一个"age"列索引,如果在"age"列中,20岁占了50%,如果现在要查询一个20岁,名叫"Tom"的人,我们则需要在表的50%的数据中查询,索引的作用大大降低。所以,我们在建立索引时要尽量将数据颗粒小的列放在索引左侧,以保证索引发挥最大的作用。

    由于索引是存储在内存(RAM)中,你应该确保该索引的大小不超过内存的限制。如果索引的大小大于内存的限制,MongoDB会删除一些索引,这将导致性能下降。

    索引不能被以下的查询使用:

    • 正则表达式及非操作符,如 $nin, $not, 等
    • 算术运算符,如 $mod, 等
    • $where 子句

    三、explain

    explain有三种模式,分别是:queryPlanner、executionStats、allPlansExecution:

    queryPlanner:不会真正的执行查询,只是分析查询,选出winning plan。

    executionStats:返回winning plan的关键数据,executionTimeMillis表示该query查询的总体时间。

    allPlansExecution:执行所有的plans。

    通常是使用 executionStats 模式
      1 MongoDB Enterprise > db.batch_info_201903.find({"phone":"13631277247","uuid":"123"}).explain("executionStats")
      2 {
      3         "queryPlanner" : {
      4                 "plannerVersion" : 1,
      5                 "namespace" : "sms.batch_info_201903",
      6                 "indexFilterSet" : false,
      7                 "parsedQuery" : {
      8                         "$and" : [
      9                                 {
     10                                         "phone" : {
     11                                                 "$eq" : "13631277247"
     12                                         }
     13                                 },
     14                                 {
     15                                         "uuid" : {
     16                                                 "$eq" : "123"
     17                                         }
     18                                 }
     19                         ]
     20                 },
     21                 "winningPlan" : {
     22                         "stage" : "FETCH",
     23                         "inputStage" : {
     24                                 "stage" : "IXSCAN",
     25                                 "keyPattern" : {
     26                                         "phone" : 1,
     27                                         "uuid" : 1
     28                                 },
     29                                 "indexName" : "phone_1_uuid_1",
     30                                 "isMultiKey" : false,
     31                                 "isUnique" : false,
     32                                 "isSparse" : false,
     33                                 "isPartial" : false,
     34                                 "indexVersion" : 1,
     35                                 "direction" : "forward",
     36                                 "indexBounds" : {
     37                                         "phone" : [
     38                                                 "["13631277247", "13631277247"]"
     39                                         ],
     40                                         "uuid" : [
     41                                                 "["123", "123"]"
     42                                         ]
     43                                 }
     44                         }
     45                 },
     46                 "rejectedPlans" : [ ]
     47         },
     48         "executionStats" : {
     49                 "executionSuccess" : true,
     50                 "nReturned" : 0,
     51                 "executionTimeMillis" : 0,
     52                 "totalKeysExamined" : 0,
     53                 "totalDocsExamined" : 0,
     54                 "executionStages" : {
     55                         "stage" : "FETCH",
     56                         "nReturned" : 0,
     57                         "executionTimeMillisEstimate" : 0,
     58                         "works" : 1,
     59                         "advanced" : 0,
     60                         "needTime" : 0,
     61                         "needYield" : 0,
     62                         "saveState" : 0,
     63                         "restoreState" : 0,
     64                         "isEOF" : 1,
     65                         "invalidates" : 0,
     66                         "docsExamined" : 0,
     67                         "alreadyHasObj" : 0,
     68                         "inputStage" : {
     69                                 "stage" : "IXSCAN",
     70                                 "nReturned" : 0,
     71                                 "executionTimeMillisEstimate" : 0,
     72                                 "works" : 1,
     73                                 "advanced" : 0,
     74                                 "needTime" : 0,
     75                                 "needYield" : 0,
     76                                 "saveState" : 0,
     77                                 "restoreState" : 0,
     78                                 "isEOF" : 1,
     79                                 "invalidates" : 0,
     80                                 "keyPattern" : {
     81                                         "phone" : 1,
     82                                         "uuid" : 1
     83                                 },
     84                                 "indexName" : "phone_1_uuid_1",
     85                                 "isMultiKey" : false,
     86                                 "isUnique" : false,
     87                                 "isSparse" : false,
     88                                 "isPartial" : false,
     89                                 "indexVersion" : 1,
     90                                 "direction" : "forward",
     91                                 "indexBounds" : {
     92                                         "phone" : [
     93                                                 "["13631277247", "13631277247"]"
     94                                         ],
     95                                         "uuid" : [
     96                                                 "["123", "123"]"
     97                                         ]
     98                                 },
     99                                 "keysExamined" : 0,
    100                                 "dupsTested" : 0,
    101                                 "dupsDropped" : 0,
    102                                 "seenInvalidated" : 0
    103                         }
    104                 }
    105         },
    106         "serverInfo" : {
    107                 "host" : "develop",
    108                 "port" : 27017,
    109                 "version" : "3.2.11",
    110                 "gitVersion" : "009580ad490190ba33d1c6253ebd8d91808923e4"
    111         },
    112         "ok" : 1
    113 }
    View Code

    对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)

    四、设计优化

    如果是插入频繁,修改多,查询较少的,可利用数据库的范式形式保存数据:

    View Code
    {
         "_id" : ObjectId("5124b5d86041c7dca81917"),
         "title" : "如何使用MongoDB", 
         "author" : [ 
                   ObjectId("144b5d83041c7dca84416"),
                   ObjectId("144b5d83041c7dca84418"),
                   ObjectId("144b5d83041c7dca84420"),
         ]
     }

    如果是查询频繁,插入修改较少的,可以全部内嵌来设计:

    View Code
    {
           "_id" : ObjectId("5124b5d86041c7dca81917"),
           "title" : "如何使用MongoDB",
           "author" : [
                    {
                             "name" : "丁磊"
                             "age" : 40,
                             "nationality" : "china",
                    },
                    {
                             "name" : "马云"
                             "age" : 49,
                             "nationality" : "china",
                    },
                    {
                             "name" : "张召忠"
                             "age" : 59,
                             "nationality" : "china",
                    },
          ]
      }

    折中设计,但是需要考虑到实际业务进行结合来寻找合适的提取字段:

    View Code
    {
           "_id" : ObjectId("5124b5d86041c7dca81917"),
           "title" : "如何使用MongoDB",
           "author" : [ 
                   {
                             "_id" : ObjectId("144b5d83041c7dca84416"),
                             "name" : "丁磊"
                    },
                    {
                             "_id" : ObjectId("144b5d83041c7dca84418"),
                             "name" : "马云"
                    },
                    {
                             "_id" : ObjectId("144b5d83041c7dca84420"),
                             "name" : "张召忠"
                    },
          ]
      }

    五、字段

    1、可为不想保存历史数据但又不想删除过多数据的集合添加过期索引,索引可加上过期时间或不添加,插入数据时可在指定字段确定是否添加过期时间

    2、组合使用

    六、其他

    热数据法

    可能你的数据集非常大,但是这并不那么重要,重要的是你的热数据集有多大,你经常访问的数据有多大(包括经常访问的数据和所有索引数据)。使用MongoDB,你最好保证你的热数据在你机器的内存大小之下,保证内存能容纳所有热数据。

    文件系统法

    MongoDB的数据文件是采用的预分配模式,并且在Replication里面,Master和Replica Sets的非Arbiter节点都是会预先创建足够的空文件用以存储操作日志。这些文件分配操作在一些文件系统上可能会非常慢,导致进程被Block。所以我们应该选择那些空间分配快速的文件系统。这里的结论是尽量不要用ext3,用ext4或者xfs。

    硬件法

    这里的选择包括了对磁盘RAID的选择,也包括了磁盘与SSD的对比选择

    七、数据存储方式

    1、主从,primary-secondary

    2、分片,shard

  • 相关阅读:
    Android VersionedGestureDetector手势事件
    Android drawBitmapMesh扭曲图像
    如何利用SVN合并代码
    Android 微信分享图文资料
    Android google map 两点之间的距离
    Android 监听ContentProvider的数据改变
    Android 自动朗读(TTS)
    Android ContentProvider的实现
    解决xcode升级之后安装的插件失效
    Android Studio 更换国内源下载依赖库
  • 原文地址:https://www.cnblogs.com/linguoguo/p/10611619.html
Copyright © 2011-2022 走看看