MongoDB 特殊索引

本文深入探讨了MongoDB的索引类型,包括单字段索引、复合索引和多键索引。单字段索引用于加速特定字段查询,复合索引支持多个字段组合查询,而多键索引适用于数组字段,为每个元素建立索引。多键索引在数组匹配和内嵌文档排序中发挥作用,但不支持覆盖查询和作为分片键。文章还介绍了分片的基本概念和分片键选择的重要性。
摘要由CSDN通过智能技术生成

mongodb的索引有很多种,其中,有一些索引和mysql相似,如单字段索引 (Single Field Index),

db.person.createIndex( {age: 1} )

上述语句针对age创建了单字段索引,其能加速对age字段的各种查询请求,是最常见的索引形式,MongoDB默认创建的id索引也是这种类型。

{age: 1} 代表升序索引,也可以通过{age: -1}来指定降序索引,对于单字段索引,升序/降序效果是一样的

又如:复合索引 (Compound Index)

db.person.createIndex( {age: 1, name: 1} )

上述索引对应的数据组织类似下表,与{age: 1}索引不同的时,当age字段相同时,在根据name字段进行排序

复合索引能满足的查询场景比单字段索引更丰富,不光能满足多个字段组合起来的查询,比如db.person.find( {age: 18, name: “jack”} ),也能满足所以能匹配符合索引前缀的查询,这里{age: 1}即为{age: 1, name: 1}的前缀,所以类似db.person.find( {age: 18} )的查询也能通过该索引来加速;但db.person.find( {name: “jack”} )则无法使用该复合索引。

但是除了这些和mysql相似的索引外,还存在一些独有的索引

多key索引 (Multikey Index)

当索引的字段为数组时,创建出的索引称为多key索引,多key索引会为数组的每个元素建立一条索引,比如person表加入一个habbit字段(数组)用于描述兴趣爱好,需要查询有相同兴趣爱好的人就可以利用habbit字段的多key索引。多键索引能够基于字符串,数字数组以及嵌套文档进行创建。

数据: {“name” : “jack”, “age” : 19, habbit: [“football, runnning”]}
// 自动创建多key索引
db.person.createIndex( {habbit: 1} )
db.person.find( {habbit: “football”} )
假定存在下列集合
{ _id: 1, item: “ABC”, ratings: [ 2, 5, 9 ] }

在habbit上创建索引
这个多键索引则包括football,runnning二个索引键,每一个分别指向相同的文档
    基于一个数组创建索引,MongoDB会自动创建为多键索引,无需刻意指定
    多键索引也可以基于内嵌文档来创建
    多键索引的边界值的计算依赖于特定的规则
    注,多键索引不等于在文档上的多列创建索引(复合索引)

    创建语法
            db.coll.createIndex( { <field>: < 1 or -1 > } )

    复合多键索引
            对于一个复合多键索引,每个索引最多可以包含一个数组。
            在多于一个数组的情形下来创建复合多键索引不被支持。

    假定存在如下集合
            { _id: 1, a: [ 1, 2 ], b: [ 1, 2 ], category: "AB - both arrays" }

    不能基于一个基于{ a: 1, b: 1 }  的多键索引,因为a和b都是数组

    假定存在如下集合
            { _id: 1, a: [1, 2], b: 1, category: "A array" }
            { _id: 2, a: 1, b: [1, 2], category: "B array" }

            则可以基于每一个文档创建一个基于{ a: 1, b: 1 }的复合多键索引
            原因是每一个索引的索引字段只有一个数组

    一些限制
            不能够指定一个多键索引为分片片键索引(MongoDB提供了基于哈希(hashed)和基于范围(Range)2种分片方式)
            哈希索引不能够成为多键索引
            多键索引不支持覆盖查询

    基于整体查询数组字段
            当一个查询筛选器将一个数组作为整体实现精确匹配时,MongoDB可以使用多键索引查找数组的第一个元素,
            但不能使用多键索引扫描寻找整个数组。相反,使用多键索引查找查询数组的第一个元素后,MongoDB检索
            相关文档并且过滤出那些复合匹配条件的文档。                       

    //创建演示文档
    > db.inventory.drop()
    > db.inventory.insertMany([
    {
      _id: 1,
      item: "abc",
      stock: [
        { size: "S", color: "red", quantity: 25 },
        { size: "S", color: "blue", quantity: 10 },
        { size: "M", color: "blue", quantity: 50 }
      ]
    },
    {
      _id: 2,
      item: "def",   // Author : Leshami
      stock: [       // Blog   : http://blog.csdn.net/leshami
        { size: "S", color: "blue", quantity: 20 },
        { size: "M", color: "blue", quantity: 5 },
        { size: "M", color: "black", quantity: 10 },
        { size: "L", color: "red", quantity: 2 }
      ]
    },
    {
      _id: 3,
      item: "ijk",
      stock: [
        { size: "M", color: "blue", quantity: 15 },
        { size: "L", color: "blue", quantity: 100 },
        { size: "L", color: "red", quantity: 25 }
      ]
    }])

    //下面基于内嵌文档的2个键来创建索引
    > db.inventory.createIndex( { "stock.size": 1, "stock.quantity": 1 } )
    {
            "createdCollectionAutomatically" : false,
            "numIndexesBefore" : 1,
            "numIndexesAfter" : 2,
            "ok" : 1
    }

    //查询内嵌文档stock.size为M的执行计划
    > db.inventory.find( { "stock.size": "M" } ).explain()
    {
     "queryPlanner" : {
        ........
        "winningPlan" : {
                "stage" : "FETCH",
                "inputStage" : {
                        "stage" : "IXSCAN",  //基于索引的扫描
                        "keyPattern" : {
                                "stock.size" : 1,
                                "stock.quantity" : 1
                        },
                        "indexName" : "stock.size_1_stock.quantity_1",
                           .......
                        "indexBounds" : {
                                "stock.size" : [
                                        "[\"M\", \"M\"]"
                                ],
                                "stock.quantity" : [
                                        "[MinKey, MaxKey]"
                                ]
                        }
                }
        },
    }

    //基于内嵌文档2个键查询的执行计划
    > db.inventory.find( { "stock.size": "S", "stock.quantity": { $gt: 20 } } ).explain()
    {
     "queryPlanner" : {
       ..........
       "winningPlan" : {
               "stage" : "FETCH",
               "filter" : {
                       "stock.quantity" : {
                               "$gt" : 20
                       }
               },
               "inputStage" : {
                       "stage" : "IXSCAN",  //此时同样也使用到了索引扫描
                       "keyPattern" : {
                               "stock.size" : 1,
                               "stock.quantity" : 1
                       },
                       "indexName" : "stock.size_1_stock.quantity_1",
                       "isMultiKey" : true,
                        .........
                       "indexBounds" : {
                               "stock.size" : [
                                       "[\"S\", \"S\"]"
                               ],
                               "stock.quantity" : [
                                       "[MinKey, MaxKey]"
               ............
     "ok" : 1
    }

    //基于内嵌数组排序查询
    > db.inventory.find( ).sort( { "stock.size": 1, "stock.quantity": 1 } ).explain()
    {
            "queryPlanner" : {
                    ........
                    "winningPlan" : {
                            "stage" : "FETCH",
                            "inputStage" : {
                                    "stage" : "IXSCAN", //内嵌数组排序查询也使用到了索引
                                    "keyPattern" : {
                                            "stock.size" : 1,
                                            "stock.quantity" : 1
                                    },
                                    "indexName" : "stock.size_1_stock.quantity_1",
                                    "isMultiKey" : true,
                                     ...........
                                    "indexBounds" : {
                                            "stock.size" : [
                                                    "[MinKey, MaxKey]"
                                            ],
                                            "stock.quantity" : [
                                                    "[MinKey, MaxKey]"
                                            ]
                                    }
                            }
                    },
                    "rejectedPlans" : [ ]
            },
    }

    //基于内嵌数组一个键作为过滤,一个键作为排序查询的执行计划
    > db.inventory.find( { "stock.size": "M" } ).sort( { "stock.quantity": 1 } ).explain()
    {
     "queryPlanner" : {
       ..........
       "winningPlan" : {
               "stage" : "FETCH",
               "inputStage" : {
                       "stage" : "IXSCAN",
                       "keyPattern" : {
                               "stock.size" : 1,
                               "stock.quantity" : 1
                       },
                       "indexName" : "stock.size_1_stock.quantity_1",
                       "isMultiKey" : true,
                       "isUnique" : false,
                       "isSparse" : false,
                       "isPartial" : false,
                       "indexVersion" : 1,
                       "direction" : "forward",
                       "indexBounds" : {
                               "stock.size" : [
                                       "[\"M\", \"M\"]"
                               ],
                               "stock.quantity" : [
                                       "[MinKey, MaxKey]"
       "rejectedPlans" : [ ]
       .......
     "ok" : 1
    }

哈希分片知识:

mongodb选择作为hash分片键的字段应该有良好的基数或者该字段包含大量不同的值,hash分片非常适合选取具有像objectId或时间戳那样单调更改的字段作为片键。

使用sh.shardCollection()方法,来对集合进行hash分片

sh.shardCollection(“database.collection”,{ : “hashed” } )
基于范围的分片会将数据划分为由片键值确定的连续范围。在范围分片模型中,具有“接近”片键的文档可能位于相同的chunk或者shard中,连续范围读取文档将变得高效,但是如果片键选择不佳,则读取和写入的效率将会降低。
如果未选择其它选项(如hash分片或者zone),则基于范围的分片是默认的分片方式。
范围分片片键的选择:
基数大
频率低
非单调变化
使用sh.shardCollection()方法,来对集合进行范围分片,可以选择单字段或者多字段
sh.shardCollection(“database.collection”,{})
分片键决定了集合内的文档如何在集群的多个分片上分布数据,分片键要么是一个索引字段,要么是一个存在于集合所有文档中的符合索引字段。MongoDB尝试在集群中的各个分片之间平均分配数据块(chunk),特别注意,shard之间平均分配的数据块(chunk),而不是数据量,分片键的选择直接关系到分片结果的好坏。

NOTE:

在MongoDB4.2之前,文档的分片字段是不可以修改的。从4.2版本开始,除非分片键是不可变的_id字段,否则你可以更新文档的分片字段。

所有需要分片的集合都必须具有支持分片的索引,即分片键上必须有索引,可以使分片键的索引,也可以是符合索引,对于符合索引,分片键必须是索引的前缀。

如果集合为空,则sh.shardCollection()在分片键上自动创建索引,无需认为干预
如果集合存在数据,则必须先创建索引,然后再使用sh.shardCollection()来为集合分片。
分片键的选择需要综合考虑分片键的基数、频率和变化率。

基数。分片键的基数决定了分片集群可以创建的最大chunk的数目。在任何给定的时间,唯一的分片值只能存在一个chunk上。例如:使用性别进行分片,则只能分为“男”和“女”2个chunk,不能随着数据增多而分裂为更多的chunk,因为一个分片值只能存储在同一个chunk中。
频率。频率代表给定值在该列中出现的比率,与关系型数据库中select distinct
…异曲同工。如果大多数文档包含了这些值的子集,那么存储这些文档的chunk将成为集群中的瓶颈,随着数据的增长,他们将会成为不可分割的数据块,降低了集群水平扩展的有效性。例如:集合people用来统计各个名族的人信息,使用名族作为分片字段,那么根据我国56个名族的人数分布,占据人口总数92%的汉族将占据一个chunk,这样会导致该chunk非常巨大,失去了分片的意义。
变化率。单调递增或单调递减的分片键可能将数据写到集群中的单个分片上。如果分片键值始终在增加,则所有新插入都将路由到以maxKey为上限的块。
如果分片键值始终在减小,则所有新插入都将路由到以minKey为下限的块。 包含该块的分片将成为写操作的瓶颈。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值