0. 概述
MongoDB 是我这几年用得比较多的数据库了,在这之前我也写过一些关于 MongoDB 的内容,例如:
但是用得多不代表懂得多,光是索引这一块我就觉得知识量有限,所以这一篇我就先对我有限的关于 MongoDB 索引的知识做个总结,如果有下一篇,那么我希望我能够学习到一些我不知道的知识,期待一下。
对于索引,那么不用多描述,都知道是用来加速查询,减少 IO 操作和延迟的一种技术,这些都是常规的索引,肯定会聊到的;但是除了简单的索引之外,MongoDB 还有一些不常规的索引,以及常规索引也有一些不常规的做法,值的来聊一聊。
1. 常规索引的常规操作
在默认情况下,MongoDB 会为每个 collection 添加一个 _id
的索引,所以默认情况下,我们单独通过 _id
来查询,总是能获得最好的性能体验,但是这在工业中却经常没那么容易,很多时候我们会使用其他字段的查询,所以如果这个时候要用到索引,就需要自己来创建了,MongoDB 创建索引的语句为: createIndex
,例如,我为我的博客中的文章 collection 创建一个文章名称的索引:
liqiang.io:PRIMARY> db.post.createIndex({"name": 1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
这样就表示我创建完索引了,当然,可以通过:getIndexes
来获取 collection 中创建的索引列表:
liqiang.io:PRIMARY> db.post.getIndexes();
[
{
"v" : 1,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "blog.post"
},
{
"v" : 1,
"key" : {
"name" : 1
},
"name" : "name_1",
"ns" : "blog.post"
}
]
如果发现创建错了,没关系,dropIndex
可以给你后悔的机会:
liqiang.io:PRIMARY> db.col.dropIndex("name_1")
{ "nIndexesWas" : 3, "ok" : 1 }
2. 普通索引的一些规则
看上去索引操作就是这么简单,但是,却是有一些不普通的规则会影响着我们创建索引,下面就来介绍几种情况:
2.1. 最左原则
所谓的最左原则是相对于一个索引包含两个或者多个字段来说的,例如我创建了这个一个索引:
db.post.createIndex({"name": 1, "status": 1, "created_at": 1})
那么隐含着 MongoDB 帮我创建了另外两个索引:
db.post.createIndex({"name": 1})
db.post.createIndex({"name": 1, "status": 1})
也就是说,当我们使用一个查询的时候,如果它们满足这三个索引中的任何一个,都是可以被这个索引优化到的,否则是不会使用到索引的:
db.post.find({"name": "mongo 索引"}) ->>>>>>>>> 会使用到索引
db.post.find({"status": “PUBLISHED”}) ->>>>>>>>> 不会使用索引
这就是所谓的最左原则了。
2.2. 排序原则
在前面你可能也发现了,创建索引的时候,需要在 field 的后面加上一个数字,这个数字可能是 1 也可能是 -1,其实它代表的意思就是创建这个索引的时候的顺序是升序(1)还是降序(-1),这个在单个字段的索引的时候影响不是很大,但是,当这个索引是一个 组合索引 的时候,事情就不一样了,可能会出现不使用索引的情况,因为 MongoDB 的要求是索引的排序要与查询的顺序一致。注意:这里是一致,而不是一样,怎么说,例如我创建了这么一个索引:
db.post.createIndex({"id": 1, "created": -1})
那么下面这两个查询是会使用索引的:
db.post.find({}).sort({id: 1, created: -1});
db.post.find({}).sort({id: -1, created: 1});
这里的顺序要么和创建索引的顺序一样,要么刚好相反;但是,下面这个查询却无法触发索引:
db.post.find({}).sort({id: 1, created: 1});
2.3. 稀疏索引
在创建索引的时候,还有一种场景需要关注,那就是如果这个索引对应的字段没有值会怎么办,是依然为它创建一个空值的索引呢,还是说直接就忽略它,这不能一概而论,得根据自己的业务需要进行,所以 MongoDB 也是以选项得形式提供支持。
需要注意的是,如果你选择了忽略这个空值,那么你通过索引是没法查询到这个空值字段所对应的记录的!!!切记!!!
2.4. 组合索引的 Tips
在使用组合索引的时候,有个原则,就是尽早缩小范围,根据最左原则,如果一个组合索引的第一个 field 就可以大大得减少后续的记录,那么就会加块后续的过滤,例如,我要做这个一个查询:
db.post.find({id: {$gt: 10}, created: {$lt: data01, $gt: date2})
一般来说,ID 的选择范围会比 created 的大得多,所以在创建索引的时候,如果考虑到这种情况会比较多,那么可以将 created 放在 ID 之前:
db.post.createIndex({created: -1, id: 1});
因为根据最左原则,直接使用 id 是不会使用到索引的,所以可以再单独创建一条新的 ID 索引:
db.post.createIndex({id: 1})
3. 特殊的索引
3.1. TTL
除了上面的常规索引之外,MongoDB 还有一个特殊的索引—TTL 索引,它是针对某个 date 字段或者 date 列表来创建的,作用就是当时间超过了这个字段的值加上 TTL 索引的设置的 expireAfterSeconds
的值之后,这条记录就会自动被 MongoDB 回收。例如下面这个例子:
db.notices.createIndex( { "createdAt": 1 }, { expireAfterSeconds: 3600 } )
表示的就是我在我的博客中创建一条通告,然后这条通告的有效期是 3600 秒,当时间到了之后,就自动删除。
4. 索引效果
前面说了那么多关于索引的内容,那么当我们提交一个查询的时候,怎么知道是否使用了索引呢?MongoDB 很贴心得为我们提供了一个 explain
函数,当你想判别一个语句是否使用了索引得时候,可以简单地通过 explain
查看,例如下面这样:
liqiang.io:PRIMARY> db.post.find({“id": 123}).explain("executeStats")
{
"cursor" : "BtreeCursor id_1",
... ...