MongoDB学习(二)一一MongoDB索引

MongoDB 索引

MongoDB的index和SQL的是一样的意思,那我们来看看MongoDB的索引是怎么使用的吧。

ensureIndex() 方法

MongoDB使用 ensureIndex() 方法来创建索引。

建立索引

1为按升序创建索引,-1为降序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> db.users.ensureIndex({"name":1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
> db.users.ensureIndex({"name":1, "sex":-1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}

索引覆盖查询

我们上面建立的索引会覆盖以下查询:

1
2
> db.users.find({name:"kelele67"}, {sex:1, _id: 0})
{ "sex" : "male" }

也就是说,对于上述查询,MongoDB的不会去数据库文件中查找。相反,它会从索引中提取数据,这是非常快速的数据查询。

而下面的实例没有排除_id,查询就不会被覆盖:

1
2
> db.users.find({name:"kelele67"}, {sex:1})
{ "_id" : ObjectId("59085a2364d6ed405ce2ad7f"), "sex" : "male" }

注意! 当所有索引字段是一个数组或者子文档时,不能使用覆盖索引查询

MongoDB 查询分析

MongoDB 查询分析可以确保我们建议的索引是否有效,是查询语句性能分析的重要工具。

MongoDB 查询分析常用函数有:

  • explain()
  • hint()

explain()

explain 操作提供了查询信息,使用索引及查询统计等。有利于我们对索引的优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
> db.users.find({name:"kelele67"}, {sex:1, _id: 0}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "kelele67.users",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$eq" : "kelele67"
}
},
"winningPlan" : {
"stage" : "PROJECTION",
"transformBy" : {
"sex" : 1,
"_id" : 0
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1,
"sex" : -1
},
"indexName" : "name_1_sex_-1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ],
"sex" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"kelele67\", \"kelele67\"]"
],
"sex" : [
"[MaxKey, MinKey]"
]
}
}
},
"rejectedPlans" : [
{
"stage" : "PROJECTION",
"transformBy" : {
"sex" : 1,
"_id" : 0
},
"inputStage" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"kelele67\", \"kelele67\"]"
]
}
}
}
},
{
"stage" : "PROJECTION",
"transformBy" : {
"sex" : 1,
"_id" : 0
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1,
"sex" : 1
},
"indexName" : "name_1_sex_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ],
"sex" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"kelele67\", \"kelele67\"]"
],
"sex" : [
"[MinKey, MaxKey]"
]
}
}
}
]
},
"serverInfo" : {
"host" : "liuqideMacBook-Pro.local",
"port" : 27017,
"version" : "3.4.2",
"gitVersion" : "3f76e40c105fc223b3e5aac3e20dcd026b83b38b"
},
"ok" : 1
}

hint()

使用 hint 来强制 MongoDB 使用一个指定的索引

1
2
> db.users.find({name:"kelele67"}, {sex:1, _id: 0}).hint({name:1, sex:1})
{ "sex" : "male" }

可以使用 explain() 函数来分析以上查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
> db.users.find({name:"kelele67"}, {sex:1, _id: 0}).hint({name:1, sex:1}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "kelele67.users",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$eq" : "kelele67"
}
},
"winningPlan" : {
"stage" : "PROJECTION",
"transformBy" : {
"sex" : 1,
"_id" : 0
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1,
"sex" : 1
},
"indexName" : "name_1_sex_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ],
"sex" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"kelele67\", \"kelele67\"]"
],
"sex" : [
"[MinKey, MaxKey]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "liuqideMacBook-Pro.local",
"port" : 27017,
"version" : "3.4.2",
"gitVersion" : "3f76e40c105fc223b3e5aac3e20dcd026b83b38b"
},
"ok" : 1
}

MongoDB 高级索引

我们插入新的users数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
> db.users.insert({
... "address": {
... "city": "ChongQing",
... "country": "China",
... "pincode": "123"
... },
... "tags": [
... "music",
... "swim",
... "blogs"
... ],
... "name": "kelele67"
... })

索引数组字段

再添加数组索引:

1
2
3
4
5
6
7
> db.users.ensureIndex({"tags":1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 4,
"numIndexesAfter" : 5,
"ok" : 1
}

前面我们也有提到:

注意! 当所有索引字段是一个数组或者子文档时,不能使用覆盖索引查询

检索集合:

1
2
> db.users.find({tags:"swim"})
{ "_id" : ObjectId("5908b8af64d6ed405ce2ad80"), "address" : { "city" : "ChongQing", "country" : "China", "pincode" : "123" }, "tags" : [ "music", "swim", "blogs" ], "name" : "kelele67" }

索引文档字段

假设我们需要通过city、country、pincode字段来检索文档,由于这些字段是子文档的字段,所以我们需要对子文档建立索引。
为子文档的三个字段创建索引,命令如下:

1
2
3
4
5
6
7
> db.users.ensureIndex({"address.city":1, "address.country":1, "address.pincode":1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 5,
"numIndexesAfter" : 6,
"ok" : 1
}

检索数据:

1
2
> db.users.find({"address.city":"ChongQing"})
{ "_id" : ObjectId("5908b8af64d6ed405ce2ad80"), "address" : { "city" : "ChongQing", "country" : "China", "pincode" : "123" }, "tags" : [ "music", "swim", "blogs" ], "name" : "kelele67" }

检索的时候要注意的除了不能使用覆盖索引查询以外,还必须按指定的索引的顺序:

1
2
> db.users.find({"address.city":"ChongQing","address.country":"China"})
{ "_id" : ObjectId("5908b8af64d6ed405ce2ad80"), "address" : { "city" : "ChongQing", "country" : "China", "pincode" : "123" }, "tags" : [ "music", "swim", "blogs" ], "name" : "kelele67" }

1
2
> db.users.find({"address.city":"ChongQing","address.country":"China","address.pincode":"123"})
{ "_id" : ObjectId("5908b8af64d6ed405ce2ad80"), "address" : { "city" : "ChongQing", "country" : "China", "pincode" : "123" }, "tags" : [ "music", "swim", "blogs" ], "name" : "kelele67" }

MongoDB 索引限制

额外开销

每个索引占据一定的存储空间,在进行插入,更新和删除操作时也需要对索引进行操作。所以,如果你很少对集合进行读取操作,建议不使用索引

内存(RAM)使用

由于索引是存储在内存(RAM)中,你应该确保该索引的大小不超过内存的限制。
如果索引的大小大于内存的限制,MongoDB会删除一些索引,这将导致性能下降

查询限制

索引不能被一下的查询使用:

  • 正则表达式及非操作符,如 $nin, $not, 等

  • 算术运算符,如 $mod, 等。

  • $where子句

经常使用explain()是一个不错的选择

限制

索引键限制

如果现有的索引字段的值超过索引键的限制,MongoDB中不会创建索引

插入文档超过索引键限制

如果文档的索引字段值超过了索引键的限制,MongoDB不会将任何文档转换成索引的集合

最大范围

  • 集合中索引不能超过64个

  • 索引名的长度不能超过125个字符

  • 一个复合索引最多可以有31个字段

MongoDB 全文检索

全文检索对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。

创建全文索引

1
2
3
4
5
6
7
> db.posts.insert({
... "post_text": "happy new year",
... "tags": [
... "67",
... "2018"
... ]
... })

我们对post_text字段建立全文索引

1
2
3
4
5
6
7
> db.posts.ensureIndex({post_text:"text"})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}

使用全文索引

我们已经对 post_text 建立了全文索引,我们可以搜索文章中的关键词 new:

1
2
> db.posts.find({$text:{$search:"new"}})
{ "_id" : ObjectId("5909471c64d6ed405ce2ad81"), "post_text" : "happy new year", "tags" : [ "67", "2018" ] }

如果想要删除全文索引,我们先找到所有索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
> db.posts.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "kelele67.posts"
},
{
"v" : 2,
"key" : {
"_fts" : "text",
"_ftsx" : 1
},
"name" : "post_text_text",
"ns" : "kelele67.posts",
"weights" : {
"post_text" : 1
},
"default_language" : "english",
"language_override" : "language",
"textIndexVersion" : 3
}
]

再进行删除:

1
2
> db.posts.dropIndex("post_text_text")
{ "nIndexesWas" : 2, "ok" : 1 }