Skip to content

索引及优化

在应用系统中,尤其在联机事务处理系统中,对数据查询及处理的速度已成为衡量应用系统成败的标准。
而采用索引来加快数据处理速度也成为广大数据库用户所接受的优化方法。
在良好的数据库设计基础上,有效地使用索引是取得高性能的基础。

MongoDB索引概述

索引是对数据库表中一列或多列的值进行排序的一种结构,用来快速寻找那些具有特定值的记录。
如果没有索引,执行查询时必须从第一个记录开始扫描整个表的所有记录,直至找到符合要求的记录。
表里面的记录数量越多,这个操作的代价就越高。
如果作为搜索条件的列上已经创建了索引,无需扫描任何记录即可迅速得到目标记录所在的位置。
例如,表有 1000 个记录,通过索引查找记录至少要比顺序扫描记录快 100 倍。
数据库索引好比是一本书前面的目录,能加快数据库的查询速度。

MongoDB 采用基于代价的优化模型,对每一个提交的有关表的查询,决定是否使用索引或用哪一个索引。
因为查询执行的大部分开销是磁盘 I/O,使用索引提高性能的一个主要目标是避免全表扫描,因为全表扫描需要从磁盘上读表的每一个数据页,如果有索引指向数据值,则查询只需读几次磁盘就可以了。
所以如果建立了合理的索引,优化器就能利用索引加速数据的查询过程。
但是,索引并不总是提高系统的性能,在增、删、改操作中索引的存在会增加一定的工作量,因此,在适当的地方增加适当的索引,并从不合理的地方删除次优的索引,有助于优化那些性能较差的应用。
实践表明,合理的索引设计是基于各种查询的分析和预测的,只有正确地使用索引与程序结合起来,才能产生最佳的优化方案。

索引也是一种 MongoDB 对象,它由一组排好序的键组成,这些键是从相应表中的一个列或多个列抽取出来的。
通常索引可以提供向表中被请求的数据行的直接指针;
如果结果集要求的顺序与索引一致,可以消除排序;
如果被请求的列都包含在索引项中,则可以避免不得不读数据行的情况

遵循以下索引优化原则,可以建立比较高效和合理的索引

  • 在索引中包括条件的所有列,可以使用索引形成的屏蔽来拒绝结果集中不合格的行
  • 对于需要排序的引用列,适当地创建索引可以避免排序
  • 考虑到管理上的开销,应避免在索引中使用多于 5 个的列
  • 对于多列索引,将查询中引用最多的列放在定义的前面
  • 不要在索引中包含经常修改或进行插入、删除的列(主关键字和外来关键字除外)

注意 "$" 符号不可以作为索引的首字母,"."不能在索引名的任何位置出现

索引操作

MongoDB 提供了多样性的索引支持,索引信息保存在system.indexes中,默认总是为_id字段创建索引。
MongoDB 索引的使用方法与 Mysql 等关系型数据库类似。
索引是凌驾于数据存储系统之上的另一层系统,因此各种结构迥异的数据库都有相同或相似的索引实现及使用接口

举例

我们创建一个拥有 1000000 个文档的集合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
MongoDB Enterprise > for (i = 0; i < 1000000; i++) {
...   db.users.insert({
...     "i": i,
...     "username": "user" + i,
...     "age": Math.floor(Math.random() * 120),
...     "created": new Date()
...   })
... }
WriteResult({ "nInserted" : 1 })
MongoDB Enterprise >
1
2
➜  mongodb du -sh .
132M    .

如果在这个集合上做查询,可以使用explain()函数查看 MongoDB 在执行查询的过程中所做的事情。

 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
MongoDB Enterprise > db.users.find({username: "user101"}).explain()
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.users",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "username" : {
                "$eq" : "user101"
            }
        },
        "queryHash" : "379E82C5",
        "planCacheKey" : "379E82C5",
        "winningPlan" : {
            "stage" : "COLLSCAN",
            "filter" : {
                "username" : {
                    "$eq" : "user101"
                }
            },
            "direction" : "forward"
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        "host" : "zhaoyangzhendeMacBook-Pro.local",
        "port" : 27017,
        "version" : "4.2.1",
        "gitVersion" : "edf6d45851c0b9ee15548f0f847df141764a317e"
    },
    "ok" : 1
}

默认只输出queryPlanner
可以给explain添加参数,参数有三个,分别是'queryPlanner''executionStats''allPlansExecution'

 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
MongoDB Enterprise > db.users.find({username: "user101"}).explain('executionStats')
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.users",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "username" : {
                "$eq" : "user101"
            }
        },
        "winningPlan" : {
            "stage" : "COLLSCAN",
            "filter" : {
                "username" : {
                    "$eq" : "user101"
                }
            },
            "direction" : "forward"
        },
        "rejectedPlans" : [ ]
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 555,
        "totalKeysExamined" : 0,
        "totalDocsExamined" : 1000000,
        "executionStages" : {
            "stage" : "COLLSCAN",
            "filter" : {
                "username" : {
                    "$eq" : "user101"
                }
            },
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 28,
            "works" : 1000002,
            "advanced" : 1,
            "needTime" : 1000000,
            "needYield" : 0,
            "saveState" : 7812,
            "restoreState" : 7812,
            "isEOF" : 1,
            "direction" : "forward",
            "docsExamined" : 1000000
        }
    },
    "serverInfo" : {
        "host" : "zhaoyangzhendeMacBook-Pro.local",
        "port" : 27017,
        "version" : "4.2.1",
        "gitVersion" : "edf6d45851c0b9ee15548f0f847df141764a317e"
    },
    "ok" : 1
}

totalDocsExamined是 MongoDB 在完成这个查询的过程中的文档总数。可以看到,这个集合中的每个文档都被扫描过了。
也就是说,为了完成这个查询,MongoDB 查看了每一个文档中的每一个字段。
executionTimeMillis表示查询耗费的毫秒数,这个查询耗费了0.5秒的时间才完成

字段nReturned显示了查询结果的数量,这里是1,因为这个集合中确实只有一个useername"user101"的文档。
注意,由于不知道集合里的username字段是唯一的,MongoDB 不得不查看集合中的每一个文档。
为了优化查询,将查询结果限制为1,这样 MongoDB 在找到一个文档之后就会停止了

 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
MongoDB Enterprise > db.users.find({'username': "user101"}).limit(1).explain('executionStats')
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.users",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "username" : {
                "$eq" : "user101"
            }
        },
        "winningPlan" : {
            "stage" : "LIMIT",
            "limitAmount" : 1,
            "inputStage" : {
                "stage" : "COLLSCAN",
                "filter" : {
                    "username" : {
                        "$eq" : "user101"
                    }
                },
                "direction" : "forward"
            }
        },
        "rejectedPlans" : [ ]
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 2,
        "totalKeysExamined" : 0,
        "totalDocsExamined" : 102,
        "executionStages" : {
            "stage" : "LIMIT",
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 0,
            "works" : 104,
            "advanced" : 1,
            "needTime" : 102,
            "needYield" : 0,
            "saveState" : 0,
            "restoreState" : 0,
            "isEOF" : 1,
            "limitAmount" : 1,
            "inputStage" : {
                "stage" : "COLLSCAN",
                "filter" : {
                    "username" : {
                        "$eq" : "user101"
                    }
                },
                "nReturned" : 1,
                "executionTimeMillisEstimate" : 0,
                "works" : 103,
                "advanced" : 1,
                "needTime" : 102,
                "needYield" : 0,
                "saveState" : 0,
                "restoreState" : 0,
                "isEOF" : 0,
                "direction" : "forward",
                "docsExamined" : 102
            }
        }
    },
    "serverInfo" : {
        "host" : "zhaoyangzhendeMacBook-Pro.local",
        "port" : 27017,
        "version" : "4.2.1",
        "gitVersion" : "edf6d45851c0b9ee15548f0f847df141764a317e"
    },
    "ok" : 1
}

现在,所扫描的文档数量极大地减少了,而且整个查询几乎是瞬间完成的。
但是这个方案是不现实的:如果要查找的是user999999呢?我们仍然不得不遍历整个集合,而且,随着用户的增加,查询会越来越慢

对于此类查询,索引是一个非常好的解决方案:索引可以根据给定的字段组织数据,让 MongoDB 能够非常快地找到目标文档。
下面尝试在username字段上创建一个索引:

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

由于机器性能和集合大小的不同,创建索引有可能需要花几分钟时间。
如果对ensureIndex的调用没能在几秒钟后返回,可以在另一个 shell 中执行db.currentOp()或者检查 mongod 的日志来查看索引创建的进程

索引创建完成之后,再次执行最初的查询

  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
MongoDB Enterprise > db.users.find({username: "user101"}).explain('executionStats')
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.users",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "username" : {
                "$eq" : "user101"
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "username" : 1
                },
                "indexName" : "username_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "username" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "username" : [
                        "[\"user101\", \"user101\"]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 4,
        "totalKeysExamined" : 1,
        "totalDocsExamined" : 1,
        "executionStages" : {
            "stage" : "FETCH",
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 0,
            "works" : 2,
            "advanced" : 1,
            "needTime" : 0,
            "needYield" : 0,
            "saveState" : 0,
            "restoreState" : 0,
            "isEOF" : 1,
            "docsExamined" : 1,
            "alreadyHasObj" : 0,
            "inputStage" : {
                "stage" : "IXSCAN",
                "nReturned" : 1,
                "executionTimeMillisEstimate" : 0,
                "works" : 2,
                "advanced" : 1,
                "needTime" : 0,
                "needYield" : 0,
                "saveState" : 0,
                "restoreState" : 0,
                "isEOF" : 1,
                "keyPattern" : {
                    "username" : 1
                },
                "indexName" : "username_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "username" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "username" : [
                        "[\"user101\", \"user101\"]"
                    ]
                },
                "keysExamined" : 1,
                "seeks" : 1,
                "dupsTested" : 0,
                "dupsDropped" : 0
            }
        }
    },
    "serverInfo" : {
        "host" : "zhaoyangzhendeMacBook-Pro.local",
        "port" : 27017,
        "version" : "4.2.1",
        "gitVersion" : "edf6d45851c0b9ee15548f0f847df141764a317e"
    },
    "ok" : 1
}

这次explain()输出内容比之前复杂一些,但是目前我们只需要注意"nReturned","totalDocsExamined","executionTimeMillis"这几个字段,可以忽略其他字段。
可以看到,这个查询现在几乎是瞬间完成的(甚至可以更好),而且对于任意username的查询,所耗费的时间基本一致:

 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
MongoDB Enterprise > db.users.find({username: "user999999"}).explain('executionStats').executionStats
{
    "executionSuccess" : true,
    "nReturned" : 1,
    "executionTimeMillis" : 0,
    "totalKeysExamined" : 1,
    "totalDocsExamined" : 1,
    "executionStages" : {
        "stage" : "FETCH",
        "nReturned" : 1,
        "executionTimeMillisEstimate" : 0,
        "works" : 2,
        "advanced" : 1,
        "needTime" : 0,
        "needYield" : 0,
        "saveState" : 0,
        "restoreState" : 0,
        "isEOF" : 1,
        "docsExamined" : 1,
        "alreadyHasObj" : 0,
        "inputStage" : {
            "stage" : "IXSCAN",
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 0,
            "works" : 2,
            "advanced" : 1,
            "needTime" : 0,
            "needYield" : 0,
            "saveState" : 0,
            "restoreState" : 0,
            "isEOF" : 1,
            "keyPattern" : {
                "username" : 1
            },
            "indexName" : "username_1",
            "isMultiKey" : false,
            "multiKeyPaths" : {
                "username" : [ ]
            },
            "isUnique" : false,
            "isSparse" : false,
            "isPartial" : false,
            "indexVersion" : 2,
            "direction" : "forward",
            "indexBounds" : {
                "username" : [
                    "[\"user999999\", \"user999999\"]"
                ]
            },
            "keysExamined" : 1,
            "seeks" : 1,
            "dupsTested" : 0,
            "dupsDropped" : 0
        }
    }
}

可以看到,使用了索引的查询几乎可以瞬间完成,这是非常激动人心的。
然而,使用索引是有代价的:对于添加的每一个索引,每次写操作(插入、更新、删除)都将耗费更多的时间。
这是因为,当数据发生变动时,MongoDB 不仅要更新文档,还要更新集合上的所有索引。
因此,MongoDB 限制每个集合上最多只能有 64 个索引。
通常,在一个特定的集合上,不应该拥有两个以上的索引。于是,挑选合适的字段建立索引非常重要

为了选择合适的键来建立索引,可以查看常用的查询,以及那些需要被优化的查询,从中找出一组常用的键。
例如,在上面的例子中,查询是在"username"上进行的。如果这是一个非常通用的查询,或者这个查询造成了性能瓶颈,那么在"username"上建立索引会是非常好的选择。
然而,如果这只是一个很少用到的查询,或者只是给管理员用的查询(管理员并不需要太在一查询耗费的时间),那就不应该对"username"建立索引

复合索引

索引的值是按一定顺序排列的,因此,使用索引值对文档进行排序非常快。
然而,只有在首先使用索引键进行排序时,索引才有用。
例如,在下面的排序里,"username"上的索引没什么作用:

1
2
3
4
5
6
7
MongoDB Enterprise > db.users.find().sort({"age": 1, "username": 1})
Error: error: {
    "ok" : 0,
    "errmsg" : "Executor error during find command :: caused by :: Sort operation used more than the maximum 33554432 bytes of RAM. Add an index, or specify a smaller limit.",
    "code" : 96,
    "codeName" : "OperationFailed"
}

这里先根据"age"排序再根据"username"排序,所以"username"在这里发挥的作用并不大。
为了优化这个排序,可能需要在"age""username"上建立索引:

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

这里就建立了一个复合索引。如果查询中有多个排序方向或者查询条件中有多个键,这个索引就会非常有用。
复合索引就是一个建立在多个字段上的索引。

1
2
3
4
5
6
7
8
9
MongoDB Enterprise > db.users.find().sort({"age": 1, "username": 1})
{ "_id" : ObjectId("5eaa1947dc76cda06839b9e6"), "i" : 100180, "username" : "user100180", "age" : 0, "created" : ISODate("2020-04-30T00:18:15.033Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839ba2c"), "i" : 100250, "username" : "user100250", "age" : 0, "created" : ISODate("2020-04-30T00:18:15.055Z") }
{ "_id" : ObjectId("5eaa1924dc76cda06838367f"), "i" : 1005, "username" : "user1005", "age" : 0, "created" : ISODate("2020-04-30T00:17:40.795Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839bb54"), "i" : 100546, "username" : "user100546", "age" : 0, "created" : ISODate("2020-04-30T00:18:15.173Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839bba8"), "i" : 100630, "username" : "user100630", "age" : 0, "created" : ISODate("2020-04-30T00:18:15.205Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839bc77"), "i" : 100837, "username" : "user100837", "age" : 0, "created" : ISODate("2020-04-30T00:18:15.287Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839bce3"), "i" : 100945, "username" : "user100945", "age" : 0, "created" : ISODate("2020-04-30T00:18:15.332Z") }
...

如果我们在这个集合上执行一个不排序(称为自然顺序)的查询:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
MongoDB Enterprise > db.users.find({}, {"_id": 0, "i": 0, "created": 0})
{ "username" : "user0", "age" : 88 }
{ "username" : "user1", "age" : 19 }
{ "username" : "user2", "age" : 44 }
{ "username" : "user3", "age" : 97 }
{ "username" : "user4", "age" : 43 }
{ "username" : "user5", "age" : 106 }
{ "username" : "user6", "age" : 50 }
{ "username" : "user7", "age" : 111 }
{ "username" : "user8", "age" : 48 }
{ "username" : "user9", "age" : 78 }
{ "username" : "user10", "age" : 94 }
{ "username" : "user11", "age" : 92 }
{ "username" : "user12", "age" : 97 }
{ "username" : "user13", "age" : 45 }
{ "username" : "user14", "age" : 81 }
{ "username" : "user15", "age" : 21 }
{ "username" : "user16", "age" : 75 }
{ "username" : "user17", "age" : 19 }
{ "username" : "user18", "age" : 73 }
{ "username" : "user19", "age" : 11 }
Type "it" for more

如果使用{"age": 1, "username": 1}建立索引,这个索引大致会是这个样子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[0, "user100309"] -> 0x0c965148
[0, "user100334"] -> 0xf51f818e
[0, "user100479"] -> 0x00fd7934
...
[0, "user99985"] -> 0xd246648f
[1, "user100156"] -> 0xf78d5bdd
[1, "user100192"] -> 0x5c7fb62
...
[1, "user999920"] -> 0x67ded4b7
[2, "user100141"] -> 0x3996dd46
[2, "user100149"] -> 0xfce68412
[2, "user100223"] -> 0x91106e23
...

每一个索引条目都包含一个"age"字段和一个"username"字段,并且指向文档在磁盘上的存储位置(这里使用十六进制数字表示)。
注意,这里的"age"字段是严格升序排列的,"age"相同的条目按照"username"升序排列。
每个"age"都有大约8000个对应的"username",这里这是挑选了少量数据用于传达大概的信息。

MongoDB 对这个索引的使用方式取决于查询的类型。下面是三种主要的方式。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
MongoDB Enterprise > db.users.find({"age": 21}).sort({"username": -1})
{ "_id" : ObjectId("5eaa1a91dc76cda0684774c4"), "i" : 999986, "username" : "user999986", "age" : 21, "created" : ISODate("2020-04-30T00:23:45.884Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b927"), "i" : 99989, "username" : "user99989", "age" : 21, "created" : ISODate("2020-04-30T00:18:14.963Z") }
{ "_id" : ObjectId("5eaa1a91dc76cda068477463"), "i" : 999889, "username" : "user999889", "age" : 21, "created" : ISODate("2020-04-30T00:23:45.857Z") }
{ "_id" : ObjectId("5eaa1a91dc76cda0684773dc"), "i" : 999754, "username" : "user999754", "age" : 21, "created" : ISODate("2020-04-30T00:23:45.811Z") }
{ "_id" : ObjectId("5eaa1a91dc76cda068477381"), "i" : 999663, "username" : "user999663", "age" : 21, "created" : ISODate("2020-04-30T00:23:45.781Z") }
{ "_id" : ObjectId("5eaa1a91dc76cda068477349"), "i" : 999607, "username" : "user999607", "age" : 21, "created" : ISODate("2020-04-30T00:23:45.765Z") }
{ "_id" : ObjectId("5eaa1a91dc76cda068477342"), "i" : 999600, "username" : "user999600", "age" : 21, "created" : ISODate("2020-04-30T00:23:45.763Z") }
{ "_id" : ObjectId("5eaa1a91dc76cda0684772df"), "i" : 999501, "username" : "user999501", "age" : 21, "created" : ISODate("2020-04-30T00:23:45.734Z") }
{ "_id" : ObjectId("5eaa1a91dc76cda068477284"), "i" : 999410, "username" : "user999410", "age" : 21, "created" : ISODate("2020-04-30T00:23:45.706Z") }
...

这是一个点查询,用于查找单个值(尽管包含这个值的文档可能有多个)。但是索引中的第二个字段,查询结果已经是有序的了:MongoDB 可以从{"age": 21}匹配的最后一饿索引开始,逆序一次遍历索引:

1
2
3
4
[21, "user999977"] -> 0x9b3160cf
[21, "user999954"] -> 0xfe039231
[21, "user999902"] -> 0x719996aa
...

这种类型的查询是非常高效地:MongoDB 能够直接定位到正确的年龄,而且不需要对结果进行排序(因为只需要对数据进行逆序遍历就可以得到正确的顺序了)

注意,排序方向并不重要:MongoDB 可以在任意方向上对索引进行遍历。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
MongoDB Enterprise > db.users.find({"age": {"$gte": 21, "$lte": 30}})
{ "_id" : ObjectId("5eaa1946dc76cda06839b946"), "i" : 100020, "username" : "user100020", "age" : 21, "created" : ISODate("2020-04-30T00:18:14.977Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839b98b"), "i" : 100089, "username" : "user100089", "age" : 21, "created" : ISODate("2020-04-30T00:18:15Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839ba0f"), "i" : 100221, "username" : "user100221", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.045Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839ba5c"), "i" : 100298, "username" : "user100298", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.069Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839ba83"), "i" : 100337, "username" : "user100337", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.087Z") }
{ "_id" : ObjectId("5eaa1927dc76cda0683859eb"), "i" : 10073, "username" : "user10073", "age" : 21, "created" : ISODate("2020-04-30T00:17:43.851Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839bc5a"), "i" : 100808, "username" : "user100808", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.274Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839bc97"), "i" : 100869, "username" : "user100869", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.303Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839bca7"), "i" : 100885, "username" : "user100885", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.309Z") }
...

这是一个多值查询,查找到多个值相匹配的文档。MongoDB 会使用索引中第一个键"age"得到匹配的文档。

通常来说,如果 MongoDB 使用索引进行查询,那么查询结果文档通常是按照索引顺序排列的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
MongoDB Enterprise > db.users.find({"age": {"$gte": 21, "$lte": 30}}).sort({"username": 1})
{ "_id" : ObjectId("5eaa1927dc76cda0683859a2"), "i" : 10000, "username" : "user10000", "age" : 24, "created" : ISODate("2020-04-30T00:17:43.828Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b946"), "i" : 100020, "username" : "user100020", "age" : 21, "created" : ISODate("2020-04-30T00:18:14.977Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b94d"), "i" : 100027, "username" : "user100027", "age" : 25, "created" : ISODate("2020-04-30T00:18:14.980Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b956"), "i" : 100036, "username" : "user100036", "age" : 25, "created" : ISODate("2020-04-30T00:18:14.983Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b95f"), "i" : 100045, "username" : "user100045", "age" : 29, "created" : ISODate("2020-04-30T00:18:14.986Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b967"), "i" : 100053, "username" : "user100053", "age" : 23, "created" : ISODate("2020-04-30T00:18:14.988Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b96c"), "i" : 100058, "username" : "user100058", "age" : 29, "created" : ISODate("2020-04-30T00:18:14.990Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b970"), "i" : 100062, "username" : "user100062", "age" : 25, "created" : ISODate("2020-04-30T00:18:14.991Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b971"), "i" : 100063, "username" : "user100063", "age" : 27, "created" : ISODate("2020-04-30T00:18:14.991Z") }
...

这是一个多值查询,与上一个类似,只是这次需要对查询结果进行排序。
跟之前一样,MongoDB 会使用索引来匹配查询条件;
然而,使用这个索引得到的结果集中"username"是无序的,而查询要求结果以"username"升序排列,所以 MongoDB 需要先在内存中对结果进行排序,然后才能返回。因此,这个查询通常不如上一个高效

当然,查询速度取决于有多少个文档与查询条件匹配:如果结果集中只有少数几个文档,MongoDB 对这些文档进行排序并不需要耗费多少时间。
如果结果集中的文档数量比较多,查询速度就会比较慢,甚至根本不能用;如果结果集的大小超过 32 MB,MongoDB 就会出错,拒绝对如此多的数据进行排序

还可以使用另一个索引(同样的键,但是顺序调换了):

1
2
3
4
5
6
7
MongoDB Enterprise > db.users.ensureIndex({"username": 1, "age": 1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 3,
    "numIndexesAfter" : 4,
    "ok" : 1
}
 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
MongoDB Enterprise > db.users.getIndexes()
[
    {
        "v" : 2,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "test.users"
    },
    {
        "v" : 2,
        "key" : {
            "username" : 1
        },
        "name" : "username_1",
        "ns" : "test.users"
    },
    {
        "v" : 2,
        "key" : {
            "age" : 1,
            "username" : 1
        },
        "name" : "age_1_username_1",
        "ns" : "test.users"
    },
    {
        "v" : 2,
        "key" : {
            "username" : 1,
            "age" : 1
        },
        "name" : "username_1_age_1",
        "ns" : "test.users"
    }
]

之前的username_1_age_1索引并没有被删除

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
MongoDB Enterprise > db.users.find({"age": {"$gte": 21, "$lte": 30}})
{ "_id" : ObjectId("5eaa1946dc76cda06839b946"), "i" : 100020, "username" : "user100020", "age" : 21, "created" : ISODate("2020-04-30T00:18:14.977Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839b98b"), "i" : 100089, "username" : "user100089", "age" : 21, "created" : ISODate("2020-04-30T00:18:15Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839ba0f"), "i" : 100221, "username" : "user100221", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.045Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839ba5c"), "i" : 100298, "username" : "user100298", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.069Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839ba83"), "i" : 100337, "username" : "user100337", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.087Z") }
{ "_id" : ObjectId("5eaa1927dc76cda0683859eb"), "i" : 10073, "username" : "user10073", "age" : 21, "created" : ISODate("2020-04-30T00:17:43.851Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839bc5a"), "i" : 100808, "username" : "user100808", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.274Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839bc97"), "i" : 100869, "username" : "user100869", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.303Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839bca7"), "i" : 100885, "username" : "user100885", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.309Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839bcc0"), "i" : 100910, "username" : "user100910", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.320Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839bcd6"), "i" : 100932, "username" : "user100932", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.327Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839bd24"), "i" : 101010, "username" : "user101010", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.361Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839bd5a"), "i" : 101064, "username" : "user101064", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.379Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839bdd3"), "i" : 101185, "username" : "user101185", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.428Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839be83"), "i" : 101361, "username" : "user101361", "age" : 21, "created" : ISODate("2020-04-30T00:18:15.498Z") }
...

现在删掉之前的age_1_username_1索引

1
2
MongoDB Enterprise > db.users.dropIndex("age_1_username_1")
{ "nIndexesWas" : 4, "ok" : 1 }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
MongoDB Enterprise > db.users.find({"age": {"$gte": 21, "$lte": 30}})
{ "_id" : ObjectId("5eaa1924dc76cda0683832a1"), "i" : 15, "username" : "user15", "age" : 21, "created" : ISODate("2020-04-30T00:17:40.458Z") }
{ "_id" : ObjectId("5eaa1924dc76cda0683832b7"), "i" : 37, "username" : "user37", "age" : 28, "created" : ISODate("2020-04-30T00:17:40.465Z") }
{ "_id" : ObjectId("5eaa1924dc76cda0683832c0"), "i" : 46, "username" : "user46", "age" : 28, "created" : ISODate("2020-04-30T00:17:40.468Z") }
{ "_id" : ObjectId("5eaa1924dc76cda0683832d4"), "i" : 66, "username" : "user66", "age" : 30, "created" : ISODate("2020-04-30T00:17:40.474Z") }
{ "_id" : ObjectId("5eaa1924dc76cda0683832d8"), "i" : 70, "username" : "user70", "age" : 28, "created" : ISODate("2020-04-30T00:17:40.476Z") }
{ "_id" : ObjectId("5eaa1924dc76cda0683832db"), "i" : 73, "username" : "user73", "age" : 27, "created" : ISODate("2020-04-30T00:17:40.477Z") }
{ "_id" : ObjectId("5eaa1924dc76cda0683832f3"), "i" : 97, "username" : "user97", "age" : 21, "created" : ISODate("2020-04-30T00:17:40.489Z") }
{ "_id" : ObjectId("5eaa1924dc76cda0683832fb"), "i" : 105, "username" : "user105", "age" : 28, "created" : ISODate("2020-04-30T00:17:40.492Z") }
{ "_id" : ObjectId("5eaa1924dc76cda068383311"), "i" : 127, "username" : "user127", "age" : 26, "created" : ISODate("2020-04-30T00:17:40.500Z") }
{ "_id" : ObjectId("5eaa1924dc76cda068383328"), "i" : 150, "username" : "user150", "age" : 26, "created" : ISODate("2020-04-30T00:17:40.507Z") }
{ "_id" : ObjectId("5eaa1924dc76cda068383336"), "i" : 164, "username" : "user164", "age" : 24, "created" : ISODate("2020-04-30T00:17:40.513Z") }
{ "_id" : ObjectId("5eaa1924dc76cda06838333c"), "i" : 170, "username" : "user170", "age" : 28, "created" : ISODate("2020-04-30T00:17:40.515Z") }
{ "_id" : ObjectId("5eaa1924dc76cda068383346"), "i" : 180, "username" : "user180", "age" : 29, "created" : ISODate("2020-04-30T00:17:40.518Z") }
{ "_id" : ObjectId("5eaa1924dc76cda068383366"), "i" : 212, "username" : "user212", "age" : 23, "created" : ISODate("2020-04-30T00:17:40.527Z") }
...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
MongoDB Enterprise > db.users.find({"age": {"$gte": 21, "$lte": 30}}).sort({"username": 1})
{ "_id" : ObjectId("5eaa1927dc76cda0683859a2"), "i" : 10000, "username" : "user10000", "age" : 24, "created" : ISODate("2020-04-30T00:17:43.828Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b946"), "i" : 100020, "username" : "user100020", "age" : 21, "created" : ISODate("2020-04-30T00:18:14.977Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b94d"), "i" : 100027, "username" : "user100027", "age" : 25, "created" : ISODate("2020-04-30T00:18:14.980Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b956"), "i" : 100036, "username" : "user100036", "age" : 25, "created" : ISODate("2020-04-30T00:18:14.983Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b95f"), "i" : 100045, "username" : "user100045", "age" : 29, "created" : ISODate("2020-04-30T00:18:14.986Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b967"), "i" : 100053, "username" : "user100053", "age" : 23, "created" : ISODate("2020-04-30T00:18:14.988Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b96c"), "i" : 100058, "username" : "user100058", "age" : 29, "created" : ISODate("2020-04-30T00:18:14.990Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b970"), "i" : 100062, "username" : "user100062", "age" : 25, "created" : ISODate("2020-04-30T00:18:14.991Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b971"), "i" : 100063, "username" : "user100063", "age" : 27, "created" : ISODate("2020-04-30T00:18:14.991Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b972"), "i" : 100064, "username" : "user100064", "age" : 27, "created" : ISODate("2020-04-30T00:18:14.991Z") }
{ "_id" : ObjectId("5eaa1946dc76cda06839b987"), "i" : 100085, "username" : "user100085", "age" : 27, "created" : ISODate("2020-04-30T00:18:14.998Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839b98b"), "i" : 100089, "username" : "user100089", "age" : 21, "created" : ISODate("2020-04-30T00:18:15Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839b990"), "i" : 100094, "username" : "user100094", "age" : 24, "created" : ISODate("2020-04-30T00:18:15.001Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839b9b3"), "i" : 100129, "username" : "user100129", "age" : 26, "created" : ISODate("2020-04-30T00:18:15.015Z") }
{ "_id" : ObjectId("5eaa1927dc76cda0683859b1"), "i" : 10015, "username" : "user10015", "age" : 25, "created" : ISODate("2020-04-30T00:17:43.833Z") }
{ "_id" : ObjectId("5eaa1947dc76cda06839b9f5"), "i" : 100195, "username" : "user100195", "age" : 30, "created" : ISODate("2020-04-30T00:18:15.037Z") }

索引管理

可以使用ensureIndex函数创建新的索引。对于一个集合,每个索引只需要创建一次。如果重复创建相同的索引,是没有任何作用的。

所有的数据库索引信息都存储在system.indexes集合中。这是一个保留集合,不能在其中插入或者删除文档。
只能通过ensureIndex或者dropIndex对其进行操作