一: 如何选择索引字段
假定我们查询语句为:
db.users.find().sort({"age":1,"username":1})
这里根据age排序再根据usename排序,上一章节建立的针对username的索引已经不起作用了。所以我们这里需要建立
复合索引(compound index),简单说来就是一个建立在多字段上的索引。
执行
db.users.ensureIndex({"age":1,"username":1})
那么这个索引大致会是这样子的:
[0,"user0001"] --> 0x0asdfs
[0,"user0002"] --> 0x0asddd
...
[23,"user0891"] --> 1x2xxdfs
...
每个索引条目都包含一个“age”字段和一个“username”字段,并且指向文档在磁盘中的存储位置。
MongoDB对这个索引的使用方式取决于查询的类型。
db.users.find({"age":21}).sort({"username":-1})
这是一个点查询,用户查找单个值。由于索引中又username,所以查询结果已经是有序的。注意:排序方向不重要,MongoDB可以在任意方向上对索引进行遍历。
这种类型的排序是非常高效的,能够直接定位到年龄,而且不需要对结果进行排序。
db.users.find({"age":{"$gte":21,"$lte":30}})
这是一个多值查询,查找到多个值相匹配的文档。
db.users.find({"age":{"$gte":21,"$lte":30}}).sort({"username":1})
这是一个多值查询,与上一个查询类似,只是需要对结果在内存中进行排序,所以效率通常不如上一个。
注意:如果结果集的大小超过32M,MongoDB排序时候就会出错。
如果对于上一个查询,使用另一个索引{"username":1,"age":1}.
db.users.find({"age":{"$gte":21,"$lte":30}}).sort({"username":1}).hint({"username":1,"age":1}).explain()
虽然这个时候查找age需要对全部索引进行扫描挑选出部分匹配的文档,但是却不需要在内存中进行排序。
因此,如果对查询结果的范围做了限制,那么MongoDB在几次匹配之后就不再扫描索引,在这种情况下,将排序键放在第一位是一个非常好的策略。
对比两个索引效率
> db.users.find({"age":{"$gte":21,"$lte":30}}).sort({"username":1}).hint({"age":1,"username":1}).explain() { "cursor" : "BtreeCursor age_1_username_1", "isMultiKey" : false, "n" : 795, ... "scanAndOrder" : true, "indexOnly" : false, "nYields" : 0, "nChunkSkips" : 0, "millis" : 6, }
> db.users.find({"age":{"$gte":21,"$lte":30}}).sort({"username":1}).hint({"username":1,"age":1}).explain() { "cursor" : "BtreeCursor username_1_age_1", "isMultiKey" : false, "n" : 795, "nscannedObjects" : 795, "nscanned" : 9834, ... "scanAndOrder" : false, "indexOnly" : false, "millis" : 27, }
对比鲜明,第二个速度还是不如第一个快。但是如果我们限制每次查询的结果数目,第二个速度就比较快了。
db.users.find({"age":{"$gte":21,"$lte":30}}).limit(10).sort({"username":1}).hint({"age":1,"username":1}).explain() { "cursor" : "BtreeCursor age_1_username_1", "isMultiKey" : false, "n" : 10, "nscannedObjects" : 795, "nscanned" : 795, ... "scanAndOrder" : true, "indexOnly" : false, "nYields" : 0, "nChunkSkips" : 0, "millis" : 5, } >
> db.users.find({"age":{"$gte":21,"$lte":30}}).limit(10).sort({"username":1}).hint({"username":1,"age":1}).explain() { "cursor" : "BtreeCursor username_1_age_1", "isMultiKey" : false, "n" : 10, "nscannedObjects" : 10, "nscanned" : 139, ... "scanAndOrder" : false, "indexOnly" : false, "nYields" : 0, "nChunkSkips" : 0, "millis" : 0, }
hint: 强制查询使用某个索引
explain : 查看查询详细数据,包含使用索引以及扫描的文档数目,是否排序等等。
二:索引类型
复合索引
覆盖索引 (covered index)
如果查询只需要查找索引中包含的字段,那就没有必要获取实际的文档。为了确保查询只需要索引就可以完成,应该使用投射。
3. 隐式索引
如果一个拥有N个键值的索引,那么你同时拥有了所有这N个键的前缀组成的索引。