MongoDB索引的学习笔记

作者:袖梨 2022-06-29


一、索引基础:

MongoDB的索引几乎与传统的关系型数据库一模一样,这其中也包括一些基本的优化技巧。下面是创建索引的命令:
> db.test.ensureIndex({“username”:1})
可以通过下面的名称查看索引是否已经成功建立:
> db.test.getIndexes()
删除索引的命令是:
> db.test.dropIndex({“username”:1})
在MongoDB中,我们同样可以创建复合索引,如:
— 数字1表示username键的索引按升序存储,-1表示age键的索引按照降序方式存储。
> db.test.ensureIndex({“username”:1, “age”:-1})
该索引被创建后,基于username和age的查询将会用到该索引,或者是基于username的查询也会用到该索引,但是只是基于age的查询将不会用到该复合索引。因此可以说,如果想用到复合索引,必须在查询条件中包含复合索引中的前N个索引列。然而如果查询条件中的键值顺序和复合索引中的创建顺序不一致的话,MongoDB可以智能的帮助我们调整该顺序,以便使复合索引可以为查询所用。如:
> db.test.find({“age”: 30, “username”: “stephen”})
对于上面示例中的查询条件,MongoDB在检索之前将会动态的调整查询条件文档的顺序,以使该查询可以用到刚刚创建的复合索引。
我们可以为内嵌文档创建索引,其规则和普通文档没有任何差别,如:
> db.test.ensureIndex({“comments.date”:1})
对于上面创建的索引,MongoDB都会根据索引的keyname和索引方向为新创建的索引自动分配一个索引名,下面的命令可以在创建索引时为其指定索引名,如:
> db.test.ensureIndex({“username”:1},{“name”:”testindex”})
随着集合的增长,需要针对查询中大量的排序做索引。如果没有对索引的键调用sort,MongoDB需要将所有数据提取到内存并排序。因此在做无索引排序时,如果数据量过大以致无法在内存中进行排序,此时MongoDB将会报错。

二、唯一索引:


在缺省情况下创建的索引均不是唯一索引。下面的示例将创建唯一索引,如:
> db.test.ensureIndex({“userid”:1},{“unique”:true})
如果再次插入userid重复的文档时,MongoDB将报错,以提示插入重复键,如:
> db.test.insert({“userid”:5})
> db.test.insert({“userid”:5})
E11000 duplicate key error index: test.test.$userid_1 dup key: { : 5.0 }
如果插入的文档中不包含userid键,那么该文档中该键的值为null,如果多次插入类似的文档,MongoDB将会报出同样的错误,如:
> db.test.insert({“userid1″:5})
> db.test.insert({“userid1″:5})
E11000 duplicate key error index: test.test.$userid_1 dup key: { : null }
如果在创建唯一索引时已经存在了重复项,我们可以通过下面的命令帮助我们在创建唯一索引时消除重复文档,仅保留发现的第一个文档,如:
–先删除刚刚创建的唯一索引。
> db.test.dropIndex({“userid”:1})
–插入测试数据,以保证集合中有重复键存在。
> db.test.remove()
> db.test.insert({“userid”:5})
> db.test.insert({“userid”:5})
–创建唯一索引,并消除重复数据。
> db.test.ensureIndex({“userid”:1},{“unique”:true,”dropDups”:true})
–查询结果确认,重复的键确实在创建索引时已经被删除。
> db.test.find()
{ “_id” : ObjectId(“4fe823c180144abd15acd52e”), “userid” : 5 }

我们同样可以创建复合唯一索引,即保证复合键值唯一即可。如:
> db.test.ensureIndex({“userid”:1,”age”:1},{“unique”:true})

 

三、松散索引

如果你的数据中一些行中没有某个字段或字段值为null,那么如果在这个字段上建立普通索引,那么无此字段或值null的行也会参与到索引结构中,占用相应的空间。如果我们不希望这些值为空的行参与到我们的索引中,这时候可以采用松散索引,松散索引只会让指定字段不为空的行参与到索引创建中来。创建一个松散索引可以用下面的命令:

db.reviews.ensureIndex({user_id: 1}, {sparse: true})

四、多值索引

MongoDB可以对一个array类型创建索引,比如像下面的结构,MongoDB可以在tags字段上创建索引:

{ name: "Wheelbarrow",

    tags: ["tools", "gardening", "soil"]

}

在生成索引时,会为tags中的三个值分别生成三个索引元素,索引中tools,gardening,soil三个值都会指向这同一行数据。相当于分裂成了三个独立的索引项。


三、使用explain:


explain是非常有用的工具,会帮助你获得查询方面诸多有用的信息。只要对游标调用该方法,就可以得到查询细节。explain会返回一个文档,而不是游标本身。如:www.111com.net
> db.test.find().explain()
{
“cursor” : “BasicCursor”,
“nscanned” : 1,
“nscannedObjects” : 1,
“n” : 1,
“millis” : 0,
“nYields” : 0,
“nChunkSkips” : 0,
“isMultiKey” : false,
“indexOnly” : false,
“indexBounds” : {

}
}
explain会返回查询使用的索引情况,耗时和扫描文档数的统计信息。
“cursor”:”BasicCursor”表示没有使用索引。
“nscanned”:1 表示查询了多少个文档。
“n”:1 表示返回的文档数量。
“millis”:0 表示整个查询的耗时。

四、索引管理:

system.indexes集合中包含了每个索引的详细信息,因此可以通过下面的命令查询已经存在的索引,如:
> db.system.indexes.find()
如果在为已有数据的文档创建索引时,可以执行下面的命令,以使MongoDB在后台创建索引,这样的创建时就不会阻塞其他操作。但是相比而言,以阻塞方式创建索引,会使整个创建过程效率更高,但是在创建时MongoDB将无法接收其他的操作。
> db.test.ensureIndex({“username”:1},{“background”:true})


创建索引命令

实际上创建索引还有更方便的命令,那就是ensureIndex,比如我们创建一个open和close两个字段的联合索引,就可以用下面的命令:

db.values.ensureIndex({open: 1, close: 1})

这个命令会触发索引创建的两个过程,一个是将相应的字段排序,因为索引是按B+树来组织的,要构建树,将数据进行排序后能够提高插入B+树的效率(第二个过程的效率),在日志中,你能看到和下面类似的输出:

Tue Jan 4 09:58:17 [conn1] building new index on { open: 1.0, close: 1.0 } for stocks.values

1000000/4308303 23%

2000000/4308303 46%

3000000/4308303 69%

4000000/4308303 92%

Tue Jan 4 09:59:13 [conn1] external sort used : 5 files in 55 secs

第二个过程是将排序好的数据插入到索引结构中,构成可用的索引:

1200300/4308303 27%

2227900/4308303 51%

2837100/4308303 65%

3278100/4308303 76%

3783300/4308303 87%

4075500/4308303 94%

Tue Jan 4 10:00:16 [conn1] done building bottom layer, going to commit

Tue Jan 4 10:00:16 [conn1] done for 4308303 records 118.942secs

Tue Jan 4 10:00:16 [conn1] insert stocks.system.indexes 118942ms

除了日志中的输出外,你还可以通过在终端执行currentOp命令来获取当前操作线程的相关信息,如下例:

> db.currentOp()

{

    "inprog" : [

        {

             "opid" : 58,

             "active" : true,

             "lockType" : "write",

             "waitingForLock" : false,

             "secs_running" : 55,

             "op" : "insert",

             "ns" : "stocks.system.indexes",

             "query" : {

         },

         "client" : "127.0.0.1:53421",

         "desc" : "conn",

         "msg" : "index: (1/3) external sort 3999999/4308303 92%"

     }

    ]

}

最后一部分就是一个索引构建过程,目前正在执行排序过程,执行到92%。

在后台创建索引 www.111com.net

创建索引会对数据库添加写锁,如果数据集比如大,会将线上读写数据库的操作挂起,以等待索引创建结束。这影响了数据库的正常服务,我们可以通过在创建索引时加background:true 的选项,让创建工作在后台执行,这时候创建索引还是需要加写锁,但是这个写锁不会直接独占到索引创建完成,而是会暂停为其它读写操作让路,不至于造成严重的性能影响。具体方法:

db.values.ensureIndex({open: 1, close: 1}, {background: true})

离线创建索引

无论如何,索引的创建都会给数据库造成一定的压力,从而影响线上服务。如果希望创建索引的过程完全不影响线上服务,我们可以通过将replica sets中的节点先从集群中剥离,在这个节点上添加相应的索引,等索引添加完毕后再将其添加到replica sets中。这只需要保证一个条件,就是创建索引的时间不能长于oplog能够保存日志的时间,否则创建完后节点再上线发现再也无法追上primary了,这时会进行resync操作。

索引备份

我们知道,无论是使用mongodump还是mongoexport命令,都只是对数据进行备份,无法备份索引。我们在恢复的时候,还是需要等待漫长的索引创建过程。所以,如果你希望备份的时候带上索引,那么最好采用备份数据文件的方式。

索引压缩

索引在使用一段时间后,经历增删改等操作,会变得比较松散,从而战用不必要的空间,我们可以通过reindex命令,重新组织索引,让索引的空间占用变得更小。

相关文章

精彩推荐