美文网首页
Mongodb基础二:索引原理与使用

Mongodb基础二:索引原理与使用

作者: joyitsai | 来源:发表于2019-05-30 15:37 被阅读0次

    1. 索引的原理?

    索引是对数据库表中一列或多列的值进行排序的一种数据结构,可以让我们查询数据库变得更快。为了方便后续介绍,先科普下MongoDB里的索引机制(同样适用于其他的数据库比如mysql)。

    > db.person.find()
    { "_id" : ObjectId("571b5da31b0d530a03b3ce82"), "name" : "jack", "age" : 19 }
    { "_id" : ObjectId("571b5dae1b0d530a03b3ce83"), "name" : "rose", "age" : 20 }
    { "_id" : ObjectId("571b5db81b0d530a03b3ce84"), "name" : "jack", "age" : 18 }
    { "_id" : ObjectId("571b5dc21b0d530a03b3ce85"), "name" : "tony", "age" : 21 }
    { "_id" : ObjectId("571b5dc21b0d530a03b3ce86"), "name" : "adam", "age" : 18 }
    

    当你往某各个集合插入多个文档后,每个文档在经过底层的存储引擎持久化后,会有一个位置信息,通过这个位置信息,就能从存储引擎里读出该文档。为方便介绍,统一用pos(position的缩写)来代表位置信息。

    比如上面的例子里,person集合里包含插入了5个文档,假设其存储后位置信息如下(为方便描述,文档省去_id字段)

    位置信息 文档
    pos1 {“name” : “jack”, “age” : 19 }
    pos2 {“name” : “rose”, “age” : 20 }
    pos3 {“name” : “jack”, “age” : 18 }
    pos4 {“name” : “tony”, “age” : 21}
    pos5 {“name” : “adam”, “age” : 18}

    假设现在有个查询 db.person.find( {age: 18} ), 查询所有年龄为18岁的人,这时需要遍历所有的文档(全表扫描),根据位置信息读出文档,对比age字段是否为18。当然如果只有4个文档,全表扫描的开销并不大,但如果集合文档数量到百万、甚至千万上亿的时候,对集合进行全表扫描开销是非常大的,一个查询耗费数十秒甚至几分钟都有可能。

    如果想加速 db.person.find( {age: 18} ),就可以考虑对person表的age字段建立索引

    db.person.createIndex( {age: 1} )  // 按age字段创建升序索引
    

    建立索引后,MongoDB会额外存储一份按age字段升序排序的索引数据,索引结构类似如下,索引通常采用类似btree的结构持久化存储,以保证从索引里快速(O(logN)的时间复杂度)找出某个age值对应的位置信息,然后根据位置信息就能读取出对应的文档。

    AGE 位置信息
    18 pos3
    18 pos5
    19 pos1
    20 pos2
    21 pos4

    简单的说,索引就是将文档按照某个(或某些)字段顺序组织起来,以便能根据该字段高效的查询。

    众所周知,MongoDB默认会为插入的文档生成_id字段(如果应用本身没有指定该字段),_id是文档唯一的标识,为了保证能根据文档id快递查询文档,MongoDB默认会为集合创建_id字段的索引。

    > db.person.getIndexes() // 查询集合的索引信息
    [
        {
            "ns" : "test.person",  // 集合名
            "v" : 1,               // 索引版本
            "key" : {              // 索引的字段及排序方向
                "_id" : 1           // 根据_id字段升序索引
            },
            "name" : "_id_"        // 索引的名称
        }
    ]
    

    2. MongoDB索引的基本使用

    2.1 单字段索引
    • 创建单字段索引:
    > db.user.ensureIndex({name: 1})
    

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

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

    • 查看当前集合的所有索引信息:
    > db.user.getIndexes()
    
    • 删除单字段索引:
    db.user.dropIndex({name: 1});
    
    2.2 复合索引:

    数字1表示name键的索引按升序存储,-1表示age键的索引按照降序方式存储。

    db.user.ensureIndex({"name":1, "age":-1})
    

    该索引被创建后,基于nameage的查询将会用到该索引,或者是基于name的查询也会用到该索引,但是只基于age的查询将不会用到该复合索引。因此可以说,如果想用到复合索引,必须在查询条件中包含复合索引中的前N个索引列

    如果查询条件中的键值顺序和复合索引中的创建顺序不一致的话,MongoDB可以智能的帮助我们调整该顺序,以便使复合索引可以为查询所用。如:

     db.user.find({"age": 30, "name": "stephen"})
    

    对于上面示例中的查询条件,MongoDB在检索之前将会动态的调整查询条件的顺序,以使该查询可以用到刚刚创建的复合索引。

    对于上面创建的索引,MongoDB都会根据索引的keyname和索引方向为新创建的索引自动分配一个索引名,下面的命令可以在创建索引时为其指定索引名(userindex),如:

    db.user.ensureIndex({"name":1},{"name":"userindex"})
    

    2.3 唯一索引

    在缺省{unique:true}的情况下创建的索引均不是唯一索引。下面将创建唯一索引,如:

     db.user.ensureIndex({"userid":1},{"unique":true})
    

    如果再次插入userid重复的文档时,MongoDB将报错,以提示插入重复键,如:

    db.user.insert({"userid":5}) 
    db.user.insert({"userid":5})
    

    E11000 duplicate key error index: user.user.$userid_1 dup key: { : 5.0 }

    如果插入的文档中不包含userid键,那么该文档中该键的值为null,如果多次插入类似的文档,MongoDB将会报出同样的错误:
    E11000 duplicate key error index: user.user.$userid_1 dup key: { : null }

    3. 使用explain

    explain是非常有用的工具,会帮助你获得查询方面诸多有用的信息:
    explain会返回查询使用的索引情况,耗时和扫描文档数的统计信息。

    3.1 explain( 'executionStats' )查询具体的执行时间
    db.tablename.find().explain( "executionStats" )
    

    输出的如下数值:explain.executionStats.executionTimeMillis就是当前数据查询所用的时间。

    参考《MongoDB索引原理》

    相关文章

      网友评论

          本文标题:Mongodb基础二:索引原理与使用

          本文链接:https://www.haomeiwen.com/subject/lboytctx.html