索引介绍
给 username 添加索引
> db.users.ensureIndex({"username":1})
索引可以让 MongoDB 能够非常快的找到文档。但是对于添加的每个索引,每次写操作(插入、更新、删除)都将花费更多的时间。在每个集合里最多只能有 64 个索引,在一个特定集合上,不应改有两个以上的索引。除非这个键经常会用到,否则不要添加索引。
当使用复合索引进行排序操作时,请将需要排序的键放在第一个
> db.users.ensureIndex({"age" : 1,"username" : 1})
> db.users.find({"age" : {"$gte":21,"$lte":30})
.sort({"username":1}).hint({"username":1,"age":1})
使用复合索引
键的方向
索引使用的方向与排序方向相同,互相反转(在每个方向上都乘上 -1)的索引是等价的:{“username”:1,”age”:-1} 和 {“username”:-1,”age”:1} 是完全一样的。只有基于多个索引进行排序时,索引方向才是比较重要的,只是基于一个键进行排序,MongoDB 可以从正反两方向读取索引。只有在多键排序时,方向才重要。
使用覆盖索引
如果你的查询只需要索引中包含的字段,不需要获取整个文档。当一个索引覆盖用户所需的所有字段,可以认为这个索引覆盖了本次查询。在实际中,优先使用覆盖索引,而不是获取整个文档。也就是说要指定不需要的字段不返回(包括 _id )。如果在一个含有数组的字段上进行查询,这个索引永远无法进行覆盖查询。
$ 操作符如何使用索引
有一些查询根本无法使用索引,有些查询使用索引会更加高效。
低效的操作符
范围
将精确匹配的字段(比如 “x” : “foo” )放在前面,将用于范围匹配的字段(比如 “y” : {“$gt” : 3, “$lt” : 5} )放在最后。这样查询可以先使用第一个索引键进行精确匹配,再使用第二个索引范围在这个结果集内部进行搜索。在一次查询中使用两个范围通常会导致低效的查询计划。
OR 查询
MongoDB 在查询时只能使用一个索引。但是 $or
是个例外, $or
可以对每个子句都使用索引,因为 $or
实际上是执行两次查询之后将结果集合并。通常来说查询两次的效率没有查询一次那么高,所以尽可能使用 $in
而不是 $or
。如果不得不使用 $or
,记住,MongoDB 需要查询每次的结果集并且从中移除重复的文档。使用 $in
查询时无法控制文档顺序(除非进行排序)。
索引对象与数组
MongoDB 允许深入文档内部,对嵌套字段和数组建立索引。嵌套对象和数组字段可以与复合索引中的顶级字段一起使用,虽然他们比较特殊,但是大多数情况下与“正常”索引字段的表现是一致的。
索引嵌套文档
对潜逃文档本身建立索引和对其某个字段建立索引是不同的。
索引数组
对数组进行索引,可以高效的搜索数组中的特定元素。对数组建立索引实际上是对每个元素建立一个索引条目,而不是数组本身,因此数组索引的代价比单值索引的代价高。每个索引中的数组字段最多只能有一个,这是为了避免在多键索引中索引条目爆炸性增长。
多键索引
对于一个索引,如果在某个文档中是一个数组,那么它会被标记为多键索引,这个行为不可逆转。如果你想让它变成非多键索引,必须保证文档中没有数组,删除新建索引才行。多键索引的引索会比非多键索引慢,因为可能有多个索引指向同一个文档,需要去重后返回。
索引基数
基数就是每个集合中每个字段拥有不同值的数量。通常一个字段的基数越高,这个键上的索引就越有用。这是因为索引能够迅速将搜索范围缩小到一个比较小的结果集。对于基数小的索引,通常无法排除大量的可能匹配。应该在基数比较高的键上建立索引,或者将它们放在前面。
使用 explain() 和 hint()
explain() 能提供大量与查询相关的信息,是重要的诊断工具。分片返回的是多个 explain() 的集合,因为查询会在多个服务器进行。不使用索引的查询的 explain() 是基础 explain() 类型。不使用索引的查询使用的是 BtreeCursor (基础游标)
explain() 显示的字段
- cursor :BtreeCursor age_1_username_1
这次查询使用什么索引。
- isMultiKey :false
本次查询是否使用了多键索引
- n :8332
本次查询的文档数量
- nscannedObjects :8332
安照索引指针去磁盘查找实际文档的次数
- nscanned :8332
如果有使用索引,表示查找过的索引条目数量。如果本次是一次全表扫描,它表示检查过的文档数量。
- scanAndOrder :false
MongoDB 是否在内存中对结果进行了排序
- indexOnly :false
MongoDB 是否只使用索引就能完成此次查询。
- nYields :0
为了让写入请求能过顺利进行,本次查询暂停的次数。
- millis :91
数据库执行本次查询所消耗的毫秒数。
- indexBounds :{..}
描述索引使用的情况,给出索引的遍历范围。
使用 hint() 可以强制指定查询使用的索引。当查询没有使用你期望的索引时使用,如果 MongoDB 不知道如何使用你指定的索引那么执行效率会降低。
查询优化器
如果一个索引能够精确匹配一个查询,查询优化器会使用这个索引。否则,将会有多个索引都适合你的查询,将会从可能的索引子集中为每次查询计划选择一个,这些查询计划并行执行。最早返回 100 个结果的就是最终计划,其他计划被终止。这个计划被缓存,下一次依旧使用该计划。如果发生了比较大的数据变动,查询优化器会重新评估计划。
何时不应该使用索引
使用索引查询时需要两次查找:
一次是查找索引条目,一次是根据索引指针去查找相应的文档。
索引通常适用的情况 | 全表扫描通常适用的情况 |
---|---|
集合较大 | 集合较小 |
文档较大 | 文档较小 |
选择性查询 | 非选择性查询 |
可以使用 $natural
进行全表扫描。
> db.entries.find({"created_id":{"$lt" : hourAgo}}).hint({"$natural" : 1})
它有个副作用: 返回的结果就是按照磁盘上的顺序排列的。对于一个活跃的数据集合是没有意义的。但是对于一个只要进行插入的工作来说,如果要得到最新的文档,就非常有用了。
索引类型
唯一索引
确保集合的每一个文档的指定键都有唯一值
让用户有不同的名字
> db.users.ensureIndex({"username":1}, {"unique":true})
超过 8K 的键不会受到唯一索引的约束:可以插入多个同样的 8K 长的字符串。
唯一复合索引
单个键的值可以相同,但所有的组合键必须是唯一的
去除重复
当在具有重复值的集合上创建索引时,需要删除重复值,再建立索引。创建索引时使用 dropDups 选项,如果遇到重复值,第一个值会被保留,之后的重复文档将会被删除。
> db.people.ensureIndex({"username":1}, {"unique":true, "dropDups":true})
dropDups 强制性创建唯一索引,你无法控制哪些文档被删除,对重要的数据不要使用它。
稀疏索引
使用 sparse 创建稀疏索引,当字段存在时它必须是唯一的(唯一性是由unique 选项决定的),它可以不存在
> db.ensureIndex({"email":1}, {"unique":true, "sparse":true})
稀疏索引可以不是唯一的,只需要去掉 unique 选项。
索引管理
数据库索引信息都保存在 system.indexes 集合中。它只能通过 ensureIndex 或者 dropIndexes 对其进行操作。
创建一个索引后,可以执行 db.collectionName.getIndexes() 来查看给定集合上的所有索引信息。
参考
[1] MongoDB权威指南