MongoDB 索引管理
索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。
MongoDB使用 createIndex() 方法来创建索引。
注意:在 3.0.0 版本前创建索引的方法为 db.collection.ensureIndex(),之后的版本使用了 db.collection.createIndex() 方法,ensureIndex() 还能用,但只是 createIndex() 的别名。
语法
createIndex()方法基本语法格式如下所示:
> db.collection.createIndex(keys, options)
语法中 Key 值为你要创建的索引字段,1 为指定按升序创建索引,如果你想按降序来创建索引指定为 -1 即可。
实例一
> db.col.createIndex({"title":1})
createIndex() 方法中你也可以设置使用多个字段创建索引(关系型数据库中称作复合索引)。
> db.col.createIndex({"title":1,"description":-1})
createIndex() 接收可选参数,可选参数列表如下:
Parameter Type Description
----------------------------------------------------------------------------------------------------
background Boolean 【建索引过程会阻塞其它数据库操作】,background可指定以后台方式创建索引,即增加 "background" 可选参数。 "background" 默认值为false。
unique Boolean 建立的索引是否唯一。指定为true创建唯一索引。默认值为false.
name string 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。
sparse Boolean 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false.
expireAfterSeconds integer 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。
v index version 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。
weights document 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。
default_language string 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。默认为英语
language_override string 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language.
实例二
在后台创建索引:
> db.values.createIndex({open: 1, close: 1}, {background: true})
通过在创建索引时加 background:true 的选项,让创建工作在后台执行,不阻塞其他数据库操作。
查看集合索引:
> db.col.getIndexes()
查看集合索引大小:
> db.col.totalIndexSize()
删除集合所有索引:
> db.col.dropIndexes()
删除集合指定索引:
> db.col.dropIndex("索引名称")
利用 TTL 集合对存储的数据进行失效时间设置:经过指定的时间段后或在指定的时间点过期,MongoDB 独立线程去清除数据。类似于设置定时自动删除任务,可以清除历史记录或日志等前提条件,设置 Index 的关键字段为日期类型 new Date()。
例如数据记录中 createDate 为日期类型时:
- 设置时间180秒后自动清除。
- 设置在创建记录后,180 秒左右删除。
> db.col.createIndex({"createDate": 1},{expireAfterSeconds: 180})
由记录中设定日期点清除。
设置 A 记录在 2019 年 1 月 22 日晚上 11 点左右删除,A 记录中需添加 "ClearUpDate": new Date('Jan 22, 2019 23:00:00'),且 Index 中 expireAfterSeconds 设值为 0。
> db.col.createIndex({"ClearUpDate": 1},{expireAfterSeconds: 0})
其他注意事项
- 索引关键字段必须是 Date 类型。
- 非立即执行:扫描 Document 过期数据并删除是独立线程执行,默认 60s 扫描一次,删除也不一定是立即删除成功。
- 单字段索引,混合索引不支持。
索引限制
额外开销
每个索引占据一定的存储空间,在进行插入,更新和删除操作时也需要对索引进行操作。所以,如果你很少对集合进行读取操作,建议不使用索引。
内存(RAM)使用
由于索引是存储在内存(RAM)中,你应该确保该索引的大小不超过内存的限制。
如果索引的大小大于内存的限制,MongoDB会删除一些索引,这将导致性能下降。
查询限制
索引不能被以下的查询使用:
- 正则表达式及非操作符,如 $nin, $not, 等。
- 算术运算符,如 $mod, 等。
- $where 子句
所以,检测你的语句是否使用索引是一个好的习惯,可以用 explain 来查看。
索引键限制
从2.6版本开始,如果现有的索引字段的值超过索引键的限制,MongoDB中不会创建索引。
插入文档超过索引键限制
如果文档的索引字段值超过了索引键的限制,MongoDB不会将任何文档转换成索引的集合。与mongorestore和mongoimport工具类似。
最大范围
- 集合中索引不能超过64个
- 索引名的长度不能超过128个字符
- 一个复合索引最多可以有31个字段
覆盖索引查询
官方的MongoDB的文档中说明,覆盖查询是以下的查询:
- 所有的查询字段是索引的一部分
- 所有的查询返回字段在同一个索引中
由于所有出现在查询中的字段是索引的一部分, MongoDB 无需在整个数据文档中检索匹配查询条件和返回使用相同索引的查询结果。因为索引存在于RAM中,从索引中获取数据比通过扫描文档读取数据要快得多。
使用覆盖索引查询
为了测试覆盖索引查询,使用以下 users 集合:
{
"_id": ObjectId("53402597d852426020000002"),
"contact": "987654321",
"dob": "01-01-1991",
"gender": "M",
"name": "Tom Benzamin",
"user_name": "tombenzamin"
}
我们在 users 集合中创建联合索引,字段为 gender 和 user_name :
>db.users.ensureIndex({gender:1,user_name:1})
现在,该索引会覆盖以下查询:
>db.users.find({gender:"M"},{user_name:1,_id:0})
也就是说,对于上述查询,MongoDB不会去数据库文件中查找。相反,它会从索引中提取数据,这是非常快速的数据查询。由于我们的索引中不包括 _id 字段,_id在查询中会默认返回,我们可以在MongoDB的查询结果集中排除它。
下面的实例没有排除_id,查询就不会被覆盖:
>db.users.find({gender:"M"},{user_name:1})
最后,如果是以下的查询,不能使用覆盖索引查询:
- 所有索引字段是一个数组
- 所有索引字段是一个子文档
高级索引
考虑以下文档集合(users ):
{
"address": {
"city": "Los Angeles",
"state": "California",
"pincode": "123"
},
"tags": [
"music",
"cricket",
"blogs"
],
"name": "Tom Benzamin"
}
以上文档包含了 address 子文档和 tags 数组。
索引数组字段
假设我们基于标签来检索用户,为此我们需要对集合中的数组 tags 建立索引。
在数组中创建索引,需要对数组中的每个字段依次建立索引。所以在我们为数组 tags 创建索引时,会为 music、cricket、blogs三个值建立单独的索引。
使用以下命令创建数组索引:
>db.users.ensureIndex({"tags":1})
创建索引后,我们可以这样检索集合的 tags 字段:
>db.users.find({tags:"cricket"})
为了验证我们使用使用了索引,可以使用 explain 命令:
>db.users.find({tags:"cricket"}).explain()
以上命令执行结果中会显示 "cursor" : "BtreeCursor tags_1" ,则表示已经使用了索引。
索引子文档字段
假设我们需要通过city、state、pincode字段来检索文档,由于这些字段是子文档的字段,所以我们需要对子文档建立索引。
为子文档的三个字段创建索引,命令如下:
>db.users.ensureIndex({"address.city":1,"address.state":1,"address.pincode":1})
一旦创建索引,我们可以使用子文档的字段来检索数据:
>db.users.find({"address.city":"Los Angeles"})
查询表达不一定遵循指定的索引的顺序,mongodb 会自动优化。所以上面创建的索引将支持以下查询:
>db.users.find({"address.state":"California","address.city":"Los Angeles"})
同样支持以下查询:
>db.users.find({"address.city":"Los Angeles","address.state":"California","address.pincode":"123"})