MongoDB索引使用注意事项

索引由单独1列字段构成,例如在cms.image集合的_srn列上创建索引,索引根据_srn列进行升序排列,如果需要降序可以使用-1,创建索引 最好采用backgroud的方式进行

db.getCollection("cms.image").createIndex({"_srn":1},{background:true});

单列索引的顺序对于排序影响不大,如果创建的是升序索引,当结果中使用降序的时候,mongo可以从索引另一端来排序。

单列索引-内嵌文档索引

内嵌文档索引:当索引字段本身也是一个集合的时候,那么这时候索引可以叫做内嵌文档索引,创建内嵌文档索引和创建其他单列索引一样,直接指定索引列字段就可以。

  • 内嵌文档索引精准匹配 在这个场景下,根据索引字段查询时,内嵌文档字段的顺序是要进准匹配的。

    例如:现有集合bios的name字段是一个内嵌文档索引,当查询语句如下 db.bios.find( { name: { first: "Yukihiro", last: "Matsumoto" } } 当我们执行上面的查询语句时,集合中name字段顺序和查询顺序相同的记录时可以匹配到索引的, first: "Yukihiro", last: "Matsumoto" 以下记录是不能匹配到name字段索引的。 first: "Yukihiro", aka: "Matz", last: "Matsumoto" last: "Matsumoto", first: "Yukihiro"

  • 内嵌文档索引查询(filed.subfiled):对于内嵌索引可以使用"filed.subfield"的方式来进行针对字段值的匹配,同样使用上面的例子当我们使用name.first和name.last进行查询的时候,可以根据值在集合中进行匹配记录,不再像上面那样进行精准字段的匹配。

    例如,执行下面的查询 db.bios.find( "name.first": "Yukihiro", "name.last": "Matsumoto" 这个查询会匹配集合中的内嵌字段name,如果name字段的first字段的值是“Yukihiro”, 并且name字段的last字段值是“Matsumoto”的文档都会被name字段索引匹配到,举个例子下面语句会被匹配。 first: "Yukihiro", aka: "Matz", last: "Matsumoto" last: "Matsumoto", first: "Yukihiro"

    在使用内嵌文档索引的时候要特别注意我们的查询语句写法。

    联合索引( Compound Indexes)

    区别于单列索引,联合索引是由多个字段组成的索引,联合索引中索引字段的顺序以及索引的排序规则都很重要,单列索引MongoDB可以自己从索引的另一端进行倒序排列,但是联合索引是先根据第一个field排序,然后在结合第一列排序的结果进行第二列字段的排序,后面的字段依此类推。 通过后台创建一个联合索引。

    db.getCollection("css.record").createIndex({"accountId":1,"corp":1,"accessKey":1,"resourceId":1},{"name":"accountId_corp_accessKey_resourceId_ASC",background:true});
    下面在集合“css.record”上创建的索引accountId_corp_accessKey_resourceId_ASC,索引由accountId,corp,accessKey,resourceId组成,那么索引的排序规则是按照accountId先进性升序排列,然后第二列corp在根据第一个accountId的记录进行升序排列,然后是第二个accountId的记录corp排序,等corp排序完成后,在使用相同的方法对于accessKey和resourceId进行排序。

    索引排序后的结果会是下面结构(顺序依次是accountId,corp,accessKey,resourceId)
    1000010001, acs, b1001, 10011
    1000010001, acs, b1001, 10012
    1000010001, hws,a2001, 10917
    1000010001, tcs, b1001, 20909
    1000010001, tcs, b2001, 10917
    1000010002, acs, a0000, 10000
    1000010002, acs, a0000, 10001
    1000010003, aaa, a0000, 00001

    索引前缀是由索引开头字段或者开始字段的子结果集组成,在使用联合索引的时候,我们不一定非要指定所有索引列为查询条件,只需要满足索引前缀,查询也会使用到索引,那么我们只要满足索引前缀使用方式索引就会生效。

    例如:现在有一个联合索引{ "item": 1, "location": 1, "stock": 1 },那么有以下的索引前缀 {item:1}, {item:1, location:1}

    那么对于MongoDB来说查询条件使用如下字段都会使用到这个索引:

  • 使用item字段
  • 使用item,location字段
  • 使用item,location,stock字段
  • 那么如果查询只使用条件item和stock的时候是否会用到索引呢?在这个场景下查询也是会用到索引,但是和前面的情况不一样的是,这时候只有item列的索引可以完全使用,stock列是无法使用到索引的,因为MongoDB中如果一个查询中省略掉了特定的索引前缀,那么在这个前缀后的字段都是无法使用到索引的。 因为省略了location字段,索引stock字段无法使用索引。

    如下情况是无法使用到索引的:

  • 使用location字段查询
  • 使用stock字段或者使用location和stock字段查询
  • 由于索引前缀的特定,因此我们在创建索引的时候,如果已经创建了{a:1,b:1}这样的联合索引,那么就没有必要在单独创建{a:1}这样的单列索引了。

    联合索引的排序

    单列索引中排序不那么的必要,因为MongoDB可以从索引的反方向进行倒序查找,但是对于联合索引来说,索引的顺序是会影响到最终的结果展示的,尤其是当我们对于结果集还要进行排序的时候。

    例如:对于集合events来说,包含两个字段username和date 如果用户想要按照username升序和date降序排序 db.evnets.find().sort({username:1, date:-1}); 或者用户想要按照username降序和date升序排序 db.evnets.find().sort({username:-1, date:1});

    对于这个场景可以选择创建索引db.events.createIndex( { "username" : 1, "date" : -1 } ),就可以满足以上两种情况。但是我们创建的一个按照升序,一个按照降序的索引是无法满足以下两个场景的。

  • db.evnets.find().sort({username:-1, date:-1})
  • db.evnets.find().sort({username:1, date:1})
  • 上面的例子索引是由两个filed组成的联合索引,那如果是3个字段或者4个字段组成的索引,我们就需要结合索引的顺序来进行实际判断,当逆着索引的方向的时候,需要考虑到所有索引字段的顺序都要逆着索引创建的顺序。

    1.联合索引查询和排序中字段顺序和索引匹配的场景

    假如现在有一个联合索引{a:1,b:1,c:1,d:1}

    db.data.createIndex( { a:1, b: 1, c: 1, d: 1 } )
    
  • 查询场景:在查询的场景里面索引字段的顺序调整不会影响到对于索引的使用。 当我们对于查询的结果没有排序的操作的时候,也就是说我们只执行查询操作 db.data.find( { b: 3, a: 4 } )db.data.find( { a: 3, b: 4 } )的时候,都是会使用到索引前缀{a:1,b:1}的
  • 排序场景:排序操作中sort()中的字段顺序要可以匹配到对应的索引前缀,否则索引不会被使用到。也就是说{a:1,b:1}的索引,可以满足sort({a:1,b:1})但是无法作用在sort({b:1,a:1})上。
  • 2.联合索引查询和排序共同组成索引前缀的场景

    根据之前的理解,我们知道了涉及到排序操作的时候,我们要特别注意字段和索引前缀的匹配,但是MongoDB中如果sort()中的排序字段不符合索引前缀,那么如果在排序操作之前有其他条件,然后这些条件等价于索引前缀也可以使用到索引。

    例如,索引{ a: 1, b: 1, c: 1, d: 1 }

    查询语句匹配到的索引前缀
    db.data.find( { a: 5 } ).sort( { b: 1, c: 1 } ){ a: 1 , b: 1, c: 1 }
    db.data.find( { b: 3, a: 4 } ).sort( { c: 1 } ){ a: 1, b: 1, c: 1 }
    db.data.find( { a: 5, b: { $lt: 3} } ).sort( { b: 1 } ){ a: 1, b: 1 }

    以上例子中都是sort操作之前的查询条件中,必须含有等价于索引前缀的字段,那么最终才可以结合sort操作中的字段,组成最终所使用的索引前缀。

    如果在sort操作之前的查询语句中没有等价的查询索引前缀,那么最终可能无法使用索引。例如一下语句无法使用索引或者无法有效的使用索引。

    //{a:1,c:1}不是合法的索引前缀
    db.data.find( { a: { $gt: 2 } } ).sort( { c: 1 } ) 
    //{c:1}不是合法的索引前缀
    db.data.find( { c: 5 } ).sort( { c: 1 } ) 
    

    因此上面的两个语句无法有效的使用{ a: 1, b: 1, c: 1, d: 1 }索引。

    MongoDB会将多个索引的交集用来适配查询语句,这个选择一般是由MongoDB的查询优化器来决定的。一般情况下索引交集都是在两个索引之间,但是也有多列索引和嵌套索引的场景。 注意:索引交集虽然可以一定程度上帮助我们解决问题,但是再做索引设计的时候,还是推荐使用联合索引而不是在初期就选择使用索引交集作为方案。

    Schema designs should not rely on index intersection. Instead, compound indexes should be used

    可以通过explain()的结果是否包含AND_SORTED 或者AND_HASH来判断是否使用了索引交集。

    单列索引交集

    单列索引的交集比较容易理解,可以参考下面例子。

    假如order集合有两个索引{qty:1}和{item:1}, MongoDB会使用索引交集结合两个索引来满足查询语句。 db.orders.find( { item: "abc123", qty: { $gt: 15 } } )

    索引前缀交集

    可以结合一下例子来查看索引前缀和单列索引的交集处理。

    假如order集合有两个索引{qty:1}和{ status: 1, ord_date: -1 }, MongoDB会使用索引交集结合两个索引来满足查询语句 db.orders.find({qty:{$gt:10} , status:‘A’} )

    索引交集和排序 在Mongo中如果sort操作和query的语句的索引是完全无关的,是无法使用索引交集将多个索引将sort操作和query操作结合起来。

    举个例子,假如集合orders有如下索引 { qty: 1 } { status: 1, ord_date: -1 } { status: 1 } { ord_date: -1 }

    那么Mongo是不会给下面的语句使用索引交集的,这是因为下面的语句sort操作和find中的索引是完全分离的。

    db.orders.find({qty: {$gt: 10}}).sort({status: 1 })
    //同样的下面语句也是无法使用到索引交集的
    db.orders.find({qty: {$gt: 10}}).sort({status: 1, ord_date:-1 })
    

    但是下面语句可以使用到索引交集,因为sort中的{ord_date:-1}和find中的status字段可以组成符合索引{ status: 1, ord_date: -1 }查询和排序共同组成索引前缀的场景。

    db.orders.find( { qty: { $gt: 10 } , status: "A" } ).sort( { ord_date: -1 } )
    
  • 查询选择性 通常我们可以通过创建索引来缩小读操作的数据范围来提高查询效率,但是在使用索引的时候如果使用不规范也会导致索引失效,从而会影响到查询的效率,除了上面提到的索引使用的一些规则之外,对于Mongo来说查询选择性也会在某一种成都场影响到对于索引的使用。
  • 查询的选择性指查询语句可以查询或者过滤集合中文档的程度,查询选择性可以决定查询是否可以有效地使用索引或者完全使用索引。换句话说就是看查询语句在集合中查找记录的选择性,是否可以很好的通过查询条件过滤出期待的结果集。

  • 高选择性:高选择性的查询可以在集合中查找到比较少比例的文档。
  • 例如,精确匹配的操作,在主键_id字段或者业务主键上进行精确匹配(等于具体的值)具有高选择性,可以在集合中选择到确定的一条记录。

  • 低选择性:低选择性的查询通常会匹配到集合中占比较大的文档,选择性如果太低可能会导致查询无法有效使用索引甚至不使用索引,这个要引起高度重视。 这就是有时候明明创建了索引,而且语句写法也规范,但是最终结果却没有使用索引,就是因为索引可能已经不在适配特定场景下的查询了,这时候需要结合业务来衡量是否要调整后者增加索引等操作来适配业务。
  • 例如,非精确的匹配操作,在索引字段上进行$nin和$ne等,随着数据量的增大不等于或者不属于某个范围的记录数据会越来越多,那么$nin和$ne的选择性就会越来越低,同理的还有$lt和$gt等, 当选择性越来越低, 即使根据索引字段来查询性能也并不一定比全表扫描高。

    说到这里并不是说这些操作就不能用,而是我们要结合数据量和实际情况来分析,另外要注意就是正则表达式查询,不同的正则匹配程度也是不尽相同的,对于查询语句可以多结合explain来看是否语句合理。

  • 覆盖查询:覆盖查询是可以完全使用索引满足的查询,并且不必检查任何文档,那么查询就不用再去集合里面寻找数据,直接从索引就可以获取到所需要的结果。
  • 查询优化器再某些操作上会进行索引优化,但是我们也可以通过使用hit()来强制使用索引,使用这个要慎重,但是在做本地测试或者性能测试的时候,可以使用hit()来查看强制使用索引的结果。

    在了解mongodb的索引使用注意事项后,研发在功能转测之前,可以自己在dev环境来检查自己的查询语句是否合理使用了索引。mongodb的执行计划可以看到索引是否有被合理使用,通过explain()来查看sql执行计划。

    对于写操作执行计划只会包含分析结果,不会真正去执行修改。

    通过db.collectionname.explain()语句来查看执行计划。

    db.cms.ins.explain().find({"_srn":"srn:ucs:uhost:cn-sh2:100001036954:instance/uhost-wtfibfs1"});
    
    { queryPlanner:    { plannerVersion: 1,     namespace: 'smart.cms.ins',     indexFilterSet: false,     parsedQuery: { _srn: { '$eq': 'srn:ucs:uhost:cn-sh2:100001036954:instance/uhost-wtfibfs1' } },     winningPlan:       { stage: 'FETCH',        inputStage:          { stage: 'IXSCAN',           keyPattern: { _srn: 1 },           indexName: '_srn_1',           isMultiKey: false,           multiKeyPaths: { _srn: [] },           isUnique: false,           isSparse: false,           isPartial: false,           indexVersion: 2,           direction: 'forward',           indexBounds: { _srn: [ '["srn:ucs:uhost:cn-sh2:100001036954:instance/uhost-wtfibfs1", "srn:ucs:uhost:cn-sh2:100001036954:instance/uhost-wtfibfs1"]' ] } } },     rejectedPlans: [] },  serverInfo:    { host: 'docker-node-dev',     port: 27017,     version: '4.0.3',     gitVersion: '7ea530946fa7880364d88c8d8b6026bbc9ffa48c' },  ok: 1 }
    可以看到结果中的queryPlanner中输出了执行计划的结果,在这里主要关注winningPlan的内容。

    执行计划模式

  • queryPlanner: 如果不制定那种模式,默认使用queryPlanner模式,queryPlanner返回查询优化器选择的计划和拒绝的计划。
  • db.cms.ins.explain("queryPlanner")

  • executionStatus: 相比于queryPlanner,executionStatus再返回查询选择计划的基础上还会返回执行的选择计划,以及对于执行计划描述的相关统计,!!#ff0000 executionStatus的返回结果包含了queryPlanner的内容,一般对于研发来说使用executionStatus。!!但是不包含执行信息的reject plann的信息。
  • db.cms.ins.explain("executionStatus")

    executionStatus模式示例:

    db.cms.ins.explain("executionStats").find({"_account":100001036954})
    
    { queryPlanner:    { plannerVersion: 1,     namespace: 'smart.cms.ins',     indexFilterSet: false,     parsedQuery: { _account: { '$eq': 100001036954 } },     winningPlan:       { stage: 'FETCH',        inputStage:          { stage: 'IXSCAN',           keyPattern: { _account: 1, _corp: 1, _accessId: 1, _region: 1 },           indexName: '_account_corp_accessId_region_ASC',           isMultiKey: false,           multiKeyPaths: { _account: [], _corp: [], _accessId: [], _region: [] },           isUnique: false,           isSparse: false,           isPartial: false,           indexVersion: 2,           direction: 'forward',           indexBounds:             { _account: [ '[100001036954.0, 100001036954.0]' ],              _corp: [ '[MinKey, MaxKey]' ],              _accessId: [ '[MinKey, MaxKey]' ],              _region: [ '[MinKey, MaxKey]' ] } } },     rejectedPlans:       [ { stage: 'FETCH',          inputStage:            { stage: 'IXSCAN',             keyPattern: { _account: 1 },             indexName: '_account_1',             isMultiKey: false,             multiKeyPaths: { _account: [] },             isUnique: false,             isSparse: false,             isPartial: false,             indexVersion: 2,             direction: 'forward',             indexBounds: { _account: [ '[100001036954.0, 100001036954.0]' ] } } } ] },  executionStats:    { executionSuccess: true,     nReturned: 63,     executionTimeMillis: 4,     totalKeysExamined: 63,     totalDocsExamined: 63,     executionStages:       { stage: 'FETCH',        nReturned: 63,        executionTimeMillisEstimate: 0,        works: 65,        advanced: 63,        needTime: 0,        needYield: 0,        saveState: 1,        restoreState: 1,        isEOF: 1,        invalidates: 0,        docsExamined: 63,        alreadyHasObj: 0,        inputStage:          { stage: 'IXSCAN',           nReturned: 63,           executionTimeMillisEstimate: 0,           works: 64,           advanced: 63,           needTime: 0,           needYield: 0,           saveState: 1,           restoreState: 1,           isEOF: 1,           invalidates: 0,           keyPattern: { _account: 1, _corp: 1, _accessId: 1, _region: 1 },           indexName: '_account_corp_accessId_region_ASC',           isMultiKey: false,           multiKeyPaths: { _account: [], _corp: [], _accessId: [], _region: [] },           isUnique: false,           isSparse: false,           isPartial: false,           indexVersion: 2,           direction: 'forward',           indexBounds:             { _account: [ '[100001036954.0, 100001036954.0]' ],              _corp: [ '[MinKey, MaxKey]' ],              _accessId: [ '[MinKey, MaxKey]' ],              _region: [ '[MinKey, MaxKey]' ] },           keysExamined: 63,           seeks: 1,           dupsTested: 0,           dupsDropped: 0,           seenInvalidated: 0 } } },  serverInfo:    { host: 'docker-node-dev',     port: 27017,     version: '4.0.3',     gitVersion: '7ea530946fa7880364d88c8d8b6026bbc9ffa48c' },  ok: 1 }
    
  • allPlansExection: allPlansExection模式下也是会返回选择查询计划和执行选择计划,但是相比于executionStatus模式,allPlansExection会返回全部的执行计划信信息。
  • db.cms.ins.explain("allPlansExection")

    执行计划结果

    !!#ff0000 执行计划包含很多属性,具体属性可以参考官方文档,一般情况下根据stages字段的结果和totalKeysExamined,totalDocsExamined,可以来判断索引使用状况!!。

    stages的结果

  • COLLSCAN 全表扫描需要避免这种情况,如果出现这个情况要创建合适的索引在查询语句上。
  • IXSCAN 索引完全匹配查询结果字段,通过索引可以完成对于查询的全部需求,不需要在结合索引到集合中抓取其他字段信息,索引使用效果良好。
  • FETCH 使用到索引,根据索引到集合中查找到对应的记录。
  • SHARD_MERGE 合并分片返回的结果。
  • SHARDING_FILTER 从分片中过滤文档
  • stages的结果比较多,我们要避免一下结果出现。

  • COLLSCAN(全表扫描)
  • SORT(使用sort但是无index)
  • SKIP 不合理的SKIP
  • SUBPLA(未用到index的$or)
  • COUNTSCAN(不使用index进行count)
  • totalKeysExamined和totalDocsExamined的结果 这两个字段都是值越小越好,值越大表示索引的区分度不高或者可能没见索引。

  • totalKeysExamined:扫描的索引条目数;
  • totalDocsExamined:检查的文档总数;
  • 关于执行计划的详细信息可以查看官方文档。

    相关链接:

  • 单列索引-内嵌文档索引
  • 索引列限制
  • 分析查询性能 www.mongodb.com/docs/v4.0/t… www.mongodb.com/docs/v4.0/r… www.mongodb.com/docs/v4.0/r… blog.csdn.net/mijichui215…
  • 分类:
    后端
  •