MongoDB使用和优化

一、前言

遇到问题可以先查看官方的用户手册。

# 当前最新版本用户手册

https://docs.mongodb.com/manual/

# v3.2版本用户手册

https://docs.mongodb.com/v3.2/

官方命令表

https://docs.mongodb.com/master/reference/command/

GitHub Awesome MongoDB资源,涵盖了MongoDB中常见的库与工具、应用列表、以及相关的文档、教程等资源。可以帮助大家进一步了解MongoDB:

地址:https://yq.aliyun.com/articles/53867

原文地址:https://github.com/ramnes/awesome-mongodb

我希望一年前就知道 MongoDB 的那些事儿

http://www.oschina.net/translate/things-i-wish-i-knew-about-mongodb-a-year-ago

二、监测MongoDB

1、使用explain来分析语句执行情况

显然,如果你有关系数据库的使用背景,你一定熟悉 explain操作,这在Mongo里面同样重要。当你为一个应用新增一个查询时,你应该在生产数据上运行一下查询以检查其速度。你也可以要求Mongo执行explain 来解释查询到底怎么执行的,以便于你可以检查索引之类的参数等等。

对该命令的官方说明:

https://docs.mongodb.com/manual/reference/command/explain/

explain有三种模式,作为explain的参数传进去:

  • queryPlanner  默认
  • executionStats
  • allPlansExecution

 

queryPlanner

queryPlanner是现版本explain的默认模式,queryPlanner模式下并不会去真正进行query语句查询,而是针对query语句进行执行计划分析并选出winning plan。

{

    "queryPlanner": {

        "plannerVersion": 1,

        "namespace": "game_db.game_user",

        "indexFilterSet": false,//针对该query是否有indexfilter(详见下文)

        "parsedQuery": {

            "w": {

                "$eq": 1

            }

        },

        "winningPlan": {  // 查询优化器针对该query所返回的最优执行计划的详细内容。

            "stage": "FETCH", //最优执行计划的stage,这里返回是FETCH,可以理解为通过返回的index位置去检索具体的文档(详见下文)

            "inputStage": { // 上一个stage的child stage,此处是IXSCAN,表示进行的是index scanning。

                "stage": "IXSCAN", 

                "keyPattern": {

                    "w": 1, //所扫描的index内容

                    "n": 1 // 返回的条数?

                },

                "indexName": "w_1_n_1", //索引名称

                "isMultiKey": false, //是否是Multikey,此处返回是false,如果索引建立在array上,此处将是true

                "direction": "forward", //此query的查询顺序,此处是forward,如果用了.sort({w:-1})将显示backward。

                "indexBounds": { //winningplan所扫描的索引范围,此处查询条件是w:1,使用的index是w与n的联合索引,故w是[1.0,1.0]而n没有指定在查询条件中,故是[MinKey,MaxKey]。

                    "w": ["[1.0, 1.0]"],

                    "n": ["[MinKey, MaxKey]"]

                }

            }

        },

        "rejectedPlans": [{ //他执行计划(非最优而被查询优化器reject的)的详细返回,其中具体信息与winningPlan的返回中意义相同

            "stage": "FETCH",

            "inputStage": {

                "stage": "IXSCAN",

                "keyPattern": {

                    "w": 1,

                    "v": 1

                },

                "indexName": "w_1_v_1",

                "isMultiKey": false,

                "direction": "forward",

                "indexBounds": {

                    "w": ["[1.0, 1.0]"],

                    "v": ["[MinKey, MaxKey]"]

                }

            }

        }]

    }

indexFilterSet

IndexFilter决定了查询优化器对于某一类型的查询将如何使用index,indexFilter仅影响查询优化器对于该类查询可以用尝试哪些index的执行计划分析,查询优化器还是根据分析情况选择最优计划。
如果某一类型的查询设定了IndexFilter,那么执行时通过hint指定了其他的index,查询优化器将会忽略hint所设置index,仍然使用indexfilter中设定的查询计划。
IndexFilter可以通过命令移除,也将在实例重启后清空。

IndexFilter的创建

db.runCommand(

   {

      planCacheSetFilter: <collection>,

      query: <query>,

      sort: <sort>,

      projection: <projection>,

      indexes: [ <index1>, <index2>, ...]

   }

)

 

db.runCommand(

   {

      planCacheSetFilter: "orders",

      query: { status: "A" },

      indexes: [

         { cust_id: 1, status: 1 },

         { status: 1, order_date: -1 }

      ]

   }

)

 

针对orders表建立了一个indexFilter,indexFilter指定了对于orders表只有status条件(仅对status进行查询,无sort等)的查询的indexes,所以以下的查询语句的查询优化器仅仅会从{cust_id:1,status:1}和{status:1,order_date:-1}中进行winning plan的选择

db.orders.find( { status: "D" } )

db.orders.find( { status: "P" } )

 

indexFilter的列表

可以通过如下命令展示某一个collecton的所有indexFilter

db.runCommand( { planCacheListFilters: <collection> } )

 

indexFilter的删除

可以通过如下命令对IndexFilter进行删除

db.runCommand(

   {

      planCacheClearFilters: <collection>,

      query: <query pattern>,

      sort: <sort specification>,

      projection: <projection specification>

   }

)

 

Stage返回参数说明

COLLSCAN #全表扫描

 

IXSCAN #索引扫描

 

FETCH #根据索引去检索指定document

 

SHARD_MERGE #将各个分片返回数据进行merge

 

SORT #表明在内存中进行了排序(与老版本的scanAndOrder:true一致)

 

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的返回

 

executionStats

该模式是mongoDB查询的执行状态,类似老版本的explain

  "executionStats": {

    "executionSuccess": true, //是否执行成功

    "nReturned": 29861, //查询的返回条数

    "executionTimeMillis": 23079, //整体执行时间 毫秒

    "totalKeysExamined": 29861, // 索引扫描次数

    "totalDocsExamined": 29861, // document扫描次数

    "executionStages": {

      "stage": "FETCH", //这里是FETCH去扫描对于documents

      "nReturned": 29861, //由于是FETCH,所以这里该值与executionStats.nReturned一致

      "executionTimeMillisEstimate": 22685,

      "works": 29862, //查看源码中发现,每次操作会加1,且会把执行时间记录在executionTimeMillis中。

      "advanced": 29861,//而在查询结束EOF,works又会加1,advanced不加。正常的返回works会比nReturned多1,这时候isEOF为true(1):另外advanced的返回值只有在命中的时候+1,在skip,eof的时候不会增加

      "needTime": 0,

      "needFetch": 0,

      "saveState": 946,

      "restoreState": 946,

      "isEOF": 1,

      "invalidates": 0,

      "docsExamined": 29861, // 与executionStats.totalDocsExamined一致

      "alreadyHasObj": 0,

      "inputStage": {

        "stage": "IXSCAN",

        "nReturned": 29861,

        "executionTimeMillisEstimate": 70,

        "works": 29862,

        "advanced": 29861,

        "needTime": 0,

        "needFetch": 0,

        "saveState": 946,

        "restoreState": 946,

        "isEOF": 1,

        "invalidates": 0,

        "keyPattern": {

          "w": 1,

          "n": 1

        },

        "indexName": "w_1_n_1",

        "isMultiKey": false,

        "direction": "forward",

        "indexBounds": {

          "w": ["[1.0, 1.0]"],

          "n": ["[MinKey, MaxKey]"]

        },

        "keysExamined": 29861,

        "dupsTested": 0,

        "dupsDropped": 0,

        "seenInvalidated": 0,

        "matchTested": 0

      }

    }

  }

 

allPlansExecution

该模式可以看做是以上两个模式加起来;

如何通过explain来分析索引

分析executionTimeMillis

"executionStats" : {

  "nReturned" : 29861,

  "totalKeysExamined" : 29861,

  "totalDocsExamined" : 29861,

  "executionTimeMillis" : 66948, # 该query的整体查询时间

  ...

  "executionStages" : {

    ...

    "executionTimeMillisEstimate" : 66244, # 该查询根据index去检索document获取29861条具体数据的时间

    ...

    "inputStage" : {

            "stage" : "IXSCAN",

            ...

           

            "executionTimeMillisEstimate" : 290, #该查询扫描29861行index所用时间

           

            ...

}

这三个值我们都希望越少越好,那么是什么影响这这三个返回值呢?

分析index与document扫描数与查询返回条目数

这里主要谈3个返回项,nReturnedtotalKeysExaminedtotalDocsExamined,分别代表该条查询返回的条目、索引扫描条目和文档扫描条目。
理想状态如下:

nReturned=totalKeysExamined & totalDocsExamined=0 (cover index,仅仅使用到了index,无需文档扫描,这是最理想状态。)

或者

nReturned=totalKeysExamined=totalDocsExamined(需要具体情况具体分析)(正常index利用,无多余index扫描与文档扫描。)

如果有sort的时候,为了使得sort不在内存中进行,我们可以在保证nReturned=totalDocsExamined的基础上,totalKeysExamined可以大于totalDocsExamined与nReturned,因为量级较大的时候内存排序非常消耗性能。

分析Stage状态

对于普通查询,我们最希望看到的组合有这些:

Fetch+IDHACK

 

Fetch+ixscan

 

Limit+(Fetch+ixscan)

 

PROJECTION+ixscan

 

SHARDING_FILTER+ixscan

 

 

不希望看到包含如下的stage:

COLLSCAN(全表扫),SORT(使用sort但是无index),不合理的SKIP,SUBPLA(未用到index的$or)

 

对于count查询,希望看到的有:

COUNT_SCAN

 

不希望看到的有:

COUNTSCAN

 

2、慢操作日志

    MongoDB自带一个非常有用的分析器。你可以根据你的需要,设定分析器只分析超过一定时间的查询。我设定是记录所有超过100微秒的查询。

// 分析所有超过100微秒的查询

db.setProfilingLevel(1, 100);

 

// 分析所有的查询

db.setProfilingLevel(2);

 

// 关闭分析器

db.setProfilingLevel(0);

 

分析器保存所有的分析数据到加了前缀的system.profile集。这和其他的数据集很像,你可以对他执行查询,例如:

// 知道最近的分析

db.system.profile.find().sort({$natural:-1});

     

//找到超过5毫秒的查询

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

     

// 找到最慢的查询

db.system.profile.find().sort({millis:-1});

 

你可以可以通过show profile  helper显示最近分析输出。

分析器本身会给每一个查询增加一些开销,但是我认为这是值得的。

 

3、使用MongoDB自带的mongostat、mongotop

字段说明,参考官方文档:

https://docs.mongodb.com/manual/reference/program/mongostat/#fields

 

 

字段说明,参考官方文档:

https://docs.mongodb.com/manual/reference/program/mongotop/#fields

4、查看最近15分钟内的服务状态

db.serverStatus() 

        // 必须在admin库下,才能使用这条命令 

        // 举个例子,我只打印出部分信息,db.serverStatus()['extra_info'],结果是 : 

 

        { 

            "note"             : "field vary by platform", 

            "heap_usage_bytes" : 62236488, 

            "page_faults"      : 24 

        } 

 

        // 其中 heap_usage_bytes 表示最近15分钟内,内存中的热数据占用的字节数 

        // 其中 page_faults      表示最近15分钟内,缺页中断的次数 

 

字段说明,参考官方文档:

https://docs.mongodb.com/manual/reference/command/serverStatus/#output

5、查看当前系统的IO情况

    举个例子 : 

 

 

disk属性值说明:

  • rrqm/s: 每秒进行 merge 的读操作数目。即 rmerge/s
  • wrqm/s: 每秒进行 merge 的写操作数目。即 wmerge/s
  • r/s: 每秒完成的读 I/O 设备次数。即 rio/s
  • w/s: 每秒完成的写 I/O 设备次数。即 wio/s
  • rsec/s: 每秒读扇区数。即 rsect/s
  • wsec/s: 每秒写扇区数。即 wsect/s
  • rkB/s: 每秒读K字节数。是 rsect/s 的一半,因为每扇区大小为512字节。
  • wkB/s: 每秒写K字节数。是 wsect/s 的一半。
  • avgrq-sz: 平均每次设备I/O操作的数据大小 (扇区)。
  • avgqu-sz: 平均I/O队列长度。
  • await: 平均每次设备I/O操作的等待时间 (毫秒)。
  • svctm: 平均每次设备I/O操作的服务时间 (毫秒)。
  • %util: 一秒中有百分之多少的时间用于 I/O 操作,即被io消耗的cpu百分比

 

 

6、使用mtools工具

    mtools最主要的功能是用来分析mongodb的日志 

    它的github的地址是 : https://github.com/rueckstiess/mtools 

    使用说明文档 地址是 : https://github.com/rueckstiess/mtools/wiki 

 

    最常用的是三个模块 : 

    mlogfilter   :  根据时间对日志切片,慢日志过滤,日志转json 

    mloginfo     :  日志统计 

    mplotqueries :  根据日志绘图 

 

    列举一些命令 : 

 

 

 

 

 

三、优化文档结构

        当面临性能问题的时候,首先考虑的是优化文档结构和优化索引,其次才是系统调优和硬件优化 

 

        在设计文档的结构的时候,一定要非常清除你需要解决的最重要的问题是什么 

        MongoDB并不是万能的许愿机,很多时候为了让你的最主要的业务流畅,你只能做出权衡

        下面提出两个原则 : 

        1. 文档结构并不取决于数据的内容,而是取决于数据的访问方式。                即,不要面向数据去设计数据库,而是面向应用去设计数据库

        2. 无计可施时,割肉全身,善用冗余来提高查询效率

 

        下面举两个案例 

        两个案例均来自imooc上面的MongoDB2014北京大会,链接为 http://www.imooc.com/learn/255

 

        案例一 

 

        存储电商的产品,每个文档形如 : 

        { 

            _id     : 一个很长的字符串(全局唯一), 

 

            china   : { 该产品的中文描述的具体的一些属性 }, 

 

            english : { 该产品的英文描述的具体的一些属性 }, 

 

            japan   : { 该产品的日文描述的具体的一些属性 }, 

 

            ...还有其他十几个语系 

        } 

        原始的索引是 { _id : 1 } 

        原始的查询语句是 db.products.find( { _id : xxx }, { china : 1 } ) 

 

        比如这里是一个中国人来查询xxx这件商品的具体信息 

        乍看之下这个设计确实没问题啊,为什么会很慢 ? 

 

        经过查日志发现,缺页中断太多,诶 ? 这关缺页中断什么事 ? 

        原因如下 : 

        1. 首先,mongodb在查询的时候,不管你有没有指定是否只返回某些字段,它都会把整个文档查出来放到内存中 

        2. 只不过在mongodb客户端(mongo-shell或者应用程序)看来,它只拿其中的china字段(当然_id是必拿的) 

        3. 所以,就算你只需要china字段,实际上内存中是存在着这个商品的全部信息 

        4. 假设总共20个语系,即内存的使用率只有 5% 

        5. 换句话说,内存原可以放20个商品,结果放了5个就满了,那当查询其余15个商品时,缺页中断率不高才怪 

 

        解决方案 : 

        拆分文档,拆分后的结构形如 :  

        { 

            _id    : 原来的_id 连接上 语系, 

            detail : { 该产品的在该语系下的详细描述 } 

        } 

 

        索引不变,现在的查询语句变为 db.products.find({ _id : xxxchina }) 

 

        这样一来,mongodb查出来的就是我要用的,内存使用率提高了20倍,缺页中断大大减少 

 

 

 

        案例二 

 

        社交圈中的动态,涉及两种文档,一是用户文档,二是动态文档。简单表示如下 : 

        用户文档 : 

        { 

            _id     : 用户id, 

            friends : [ 朋友1的id, 朋友2的id, ... ] 

        } 

        用户文档的索引 { _id : 1 } 

        动态文档 : 

        { 

            creator : 发布者的id, 

            time    : 发布的时间戳, 

            content : 动态的内容 

        } 

        动态文档的索引 { creator : 1, time : -1 } 

        要查出我的所有朋友最近的几条动态,需要做联合查询,而且是带in的联合查询,慢是正常的 

 

        解决方案 : 

        在每发布一条动态的时候,顺带加上当前我的所有朋友,修改后的动态文档的结构 : 

        { 

            creator : 发布者的id, 

            time    : 发布的时间戳, 

            friends : [ 此时的朋友1的id, 此时的朋友2的id, ... ], 

            content : 动态的内容 

        } 

        另外为这个文档增加索引 { friends : 1, time : -1 } 

        现在的查询语句就不是联合查询了,会快很多 

 

        但是这个解决方案很奇葩是么,搞这么大的冗余,多浪费磁盘啊,而且一致性的维护怎么做? 

        但是,需要牢记的是,你的目标是啥?你需要解决的最大的问题是啥? 

        如果你的应用,容忍一定程度的不一致性,比如社交圈这种,肯定是要优先照顾读的性能,通过冗余来提高读性能是很常见的 

 

        你可能会有疑问 : 

        案例一是因为要查的信息占整个文档的比例太小,所以内存利用率不高导致缺页中断频繁 

        照这个道理,案例二中增加了很大的冗余信息,不是也会导致频繁的缺页中断吗,为什么案例二就可以用冗余呢 

        此两者不是很矛盾吗? 

 

        我一开始也有这个疑惑,但是我要说的是,你会这么想,是不自觉地犯了根据数据内容来设计数据库的老病 

        具体分析一下 : 

 

        案例一存储的是电商商品的信息,所有人都可能会去查询一些热门商品的信息 

        也就是说,商品信息存在热数据,而且是被所有人都可能访问的热数据 

        所以,在这种应用场景下,为了让热数据使用率更高,当然要提高内存利用率,即减少缺页中断的次数 

        既然要提高内存的利用率,就要使得用户查的信息占整个文档的比例尽可能大 

        这样一来,才能让固定的内存去装的下更多数量的文档 

        所以,需要把一个商品的文档按照语系来切分成不同的文档 

        这样一来,那些罕见语系的商品信息基本不会占用内存,内存中都是热门商品的常用语系的信息 

 

        案例二存储的是不同用户发表的动态信息 

        第一,不同人的动态是不同的。第二,而且一般查的时候是翻页来查的 

        这两个原因就决定了这种数据是不存在热数据的概念的 

        所以,绝大多数情况,都是不可避免的缺页中断,都是去磁盘上查 

        既然无法使用热数据,既然不可避免地查磁盘,那就只能让查磁盘更快这条路了 

        所以,使用冗余是合适的 

 

        对比分析了这两个案例,以及你可能出现的疑惑,相信你有了更深刻的认识 

        再次强调 : 

        不要面向数据去设计数据库,而是要面向应用去设计数据库

 

 

四、片键、索引思考

使用mongo进行分布式存储的时候,片键和索引的选取至关重要。

1、分布式存储的两大需求

1)查询局部化

根据查询条件,能够定位到某一台机子或者几台机子上去做查询,即最好不要查询所有的机子。

2)数据均衡性

每一台机子的数据量要差不多,即不能让数据过分集中于某几台机子,那样会导致那几台机子太累而成为性能的瓶颈,而其他机子太空闲。

2、分片片键和查询索引的一般原则

片键最好是索引的前缀

比如,我的分片片键是{ a },我的查询索引是{ a, b }。它隐含的要求是,一是首先这个{ a }片键要能够在插入新的数据时,比较均衡地把数据分散到不同的机子上。二是我们大多数的查询是使用{ a, b }作为查询条件的,这样才能快速地把查询定位到少数的几台机子上。

原则上是这样,最大的难题是:

1. 往往我们很难找到这样的片键和索引,即很多时候,我们找不到这样的a和b,或者说我们的有效数据中找不到这样的字段。需要我们利用有效数据字段,来人为构造出这样的a和b。

2. 片键和索引往往无法满足上述说的分布式存储的两大需求,甚至很多时候这两个需求是有矛盾的

矛盾情况1 : 比如我们可以用简单的Btree索引来作为片键,由于Btree索引是比较聚集的,能够使得我们的查询是局部化的。但是Btree索引往往导致数据在各个机子上很不均衡,这也是由于这种索引比较聚集的原因。设想一下,你有三个分片结点,你的片键是一个整数字段,像一般的自动分片,会把[0,100]的分到第一个分片,把[101,1000]的分到第二个结点,剩下的分到第三个结点,你也发现了吧,无论怎样分,都无法保证三个区间的长度相等,所以结果是违反了数据均衡性的需求。

矛盾情况2 : 比如我们可以用简单的hash索引来作为片键,毫无疑问,只要你的字段基数足够大(基数大的意思即取值范围比较大),数据的均衡性是可以靠哈希的分散性来保证的。但是,当你做查询的时候,如果你的查询条件不是这个字段呢,就只能查询所有的分片结点再汇合了,违反了查询局部化的需求。因为我们的查询条件很可能是多字段的,而目前的哈希索引还只能支持单键,所以你脑袋中设想的类似{ key1 : hashed, key2 : 1, key3 : 1 }的多级索引或多级片键是无法实现的,如果实现了这种复合哈希索引,那问题就彻底解决了,可是目前还未实现...所以单纯地使用哈希来进行分片,很多时候是无法解决问题的。

 

3、举例说明如何设计片键和索引

数据是用户发表的动态。考虑到每个用户会发表很多动态,可能每天就有好几条,所以合理的查询是按时间降序分页查询,或者是按时间段进行查询,总之不会一次性就返回所有的记录。我们拿按时间段查询为例,来讲述几种方案的优劣:

3-1. 分片片键={ user : 1, year : 1, month : 1 },查询索引={ user : 1, year : 1, month : 1, day : 1 }。让片键成为索引的前缀,感觉上去既达到了分片效果,又能使用这个四级索引很快地定位到某几台机子上,快速查出某年某月某日的所有动态,或者查出某年某月的所有动态。然而实际情况是,片键的第一个字段的基数太大,这种大基础的升序索引,它的均衡性是很差的。当然像mongo提供了手动分片,你可以根据user的前几位字符来手动制定路由规则,但是一般的自动分片不推荐使用大基数的字段作为片键的第一个字段。

3-2. 分片片键={ month : 1, user : 1 },查询索引={ month : 1, user : 1, year : 1, day : 1 }。使用month作为片键的主字段,好处是month是一个周期性的数据,且基数不是太大。记住千万不要用递增的字段来作为片键的主字段,设想一下,你有10台分片机子,你用时间戳来作为片键的主字段,那么随着时间的推移,有些机子将再也不会有新的数据,这是及其不好的。而使用month就恰恰避免了这种情况,而且片键的副字段user是一个基数很大的字段,它可以用来对分片内部的数据块进行分割。然而,month这个字段的基数太小,如果我们的集群规模变大,超过12,那么多余的机子的利用率会非常低下。

3-3. 分片片键={ shard : 1, user : 1 },查询索引={ shard : 1, user : 1, year : 1, month : 1, day : 1 }。其中shard是一个人为构造的字段,shard = func( year, month ),具体的算法是,比如日期是20160607,那么shard = (2+1)×(0+1)×(1+1)×(6+1)×06 = 252。这个片键的好处是,shard的基数既不太大也不太小,而且shard这个值随着月份年份的增加,并不是一直递增的。这是一个比较简单的算法。

 

 

 

 

4、举例说明不能随便套用片键

数据是用户维护的项目,它和上面论述的用户的动态内容一样,本质上都是”某人发布了啥“的记录。

乍一看,可以套用上面3-3中的方法,即分片片键={ shard : 1, user : 1 },查询索引={ shard : 1, user : 1, year : 1, month : 1, day : 1 },似乎没什么问题。但是忽略了一点,它们的数据量的差别是非常大的。对于用户发布的动态,一天就好几条,一下子返回所有记录是不合适的,按照时间段来查询是合适的。

然而对于用户维护的项目,一个用户几年下来,维护的项目基本上也不会超过10个,还要必要分时间段查询吗?没有必要,而且是不能这么做。设想一下,你在客户端上点了半天,也没点出来一个项目,好不容易点到一个月份有项目,这用户体验也太差了吧。所以,这种情况下,一次性返回所有的项目,反而是个合适的做法。

既然查询方式发生了变化,那么我们的查询索引就要变,分片片键也就跟着要变,保持片键是索引的前缀。我的做法是这样的:

对项目表,分片片键={ _id : hashed },查询索引={ _id : hashed }。对用户表,设置一个projects字段,它的值like [ id1, id2 ]。在每次往项目表插入一条数据的时候,记下这条新数据的自动生成的文档id,插入到对应的用户的projects字段中。这样一来,项目数据能达到很好的均衡性,而且可以通过查用户表快速获得该用户所有的项目id,再通过id迅速路由到指定的分片结点上去查询。

5、片键、索引总结

1)分片片键是查询索引的前缀

2)除了哈希片键,非哈希片键建议使用多级片键

3)多级片键中,片键的第一个字段最好不要是递增的

4)多级片键中,片键的第一个字段的基数不要太大,也不要太小

5)多级片键中,片键的第二个字段的基数大些比较好

6)在找不到合适的字段做片键时,根据已有字段,构造新字段

7)数据量的不同,查询方式便不同,片键与索引跟着也不同

 

关于索引,要进一步研究可以参考这些文章:

# mongodb调优那些事(二)-索引

http://blog.csdn.net/zxmsdyz/article/details/50925402

 

 

猜你喜欢

转载自blog.csdn.net/b0207191/article/details/89098536
今日推荐