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

2019-05-30  本文已影响0人  joyitsai

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索引原理》

上一篇下一篇

猜你喜欢

热点阅读