前提:测试数据库有100万条数据,分别存放{"user":"user: 0-1000000","age:"0-100","createAt":new Date()}字段
- 基于多个字段查询时,尽量将会用于精确查询的字段放在索引前面,范围查询字段放在索引后面。
例如: 查询小于"user: 1080"的user字段并且年龄是40岁的人。
√
db.foo.find({"user":{"$lt":"user: 1080"},"age":40}).hint({"age":1,"user":1})
×
db.foo.find({"user":{"$lt":"user: 1080"},"age":40}).hint({"user":1,"age":1})
- 单、多个索引时,多字段排序应该将索引字段放置第一位,排序字段放在末尾。
例如:根据"user"字段升序,"age"字段升序:
(单字段索引)
√
db.foo.find().sort({"user":1,"age":1}).hint({"user":1})
×
db.foo.find().sort({"user":1,"age":1}).hint({"age":1})
(多字段索引)
√
db.foo.find({"age":21}).sort({"user":-1}).hint({"age":1,"user":1})
×
db.foo.find({"age":21}).sort({"user":-1}).hint({"user":1,"age":1})
- MongoDB会对已经添加索引的字段根据你的指示进行排序,查找单个值,根据第二个字段排序不会造成性能影响。
例如:查找年龄为21岁的用户并且根据用户倒序排列:
√
db.foo.find({"age":21}).sort({"user":-1}).hint({"age":1,"user":1})
该索引里已经是有序的了,即便是倒序,MongoDB也会根据age字段搜索出复合条件的条目然后再逆遍历输出。
PS:db.collection.ensureIndex({"age":1,"user":1})MongoDB索引会先根据age进行升序,然后age的相同条目再会由user进行升序排序。
(-1则是倒序排序)。
- 根据范围且要排序的查询:
例如:查询年龄介于15-30之间并且根据user降序处理:
√
db.foo.find({"age":{"$gt":15,"$lt":30}}).sort({"user":-1}).hint({"age":1,"user":1})//该方法的排序操作在内存中进行。
×
db.foo.find({"age":{"$gt":15,"$ lt":30}}).sort({"user":-1}).hint({"user":1,"age":1})//该方法的排序操作不用在内存中进行。
不在内存中进行排序会慢于将排序放于内存中进行。在内存中进行大量数据排序会牺牲性能,无可厚非,但是如果数据过多(超过32MB),则MongoDB会出错,所以建议不要在内存里面进行排序;并且如果限制查询范围,MongoDB在进行几次匹配之后不再匹配索引,在这种情况下,将排序键放在前面是一个非常好的策略。
- 根据范围且要排序且限制输出的查询:
√
db.foo.find({"age":{"$gt":15,"$lt":30}}).sort({"user":-1}).limit(1000).hint({"user":1,"age":1})//该方法的排序操作不在内存中进行。
//executionTimeMillis: 16
//totalKeysExamined: 8693
×
db.foo.find({"age":{"$gt":15,"$lt":30}}).sort({"user":-1}).limit(1000).hint({"age":1,"user":1})//该方法的排序操作在内存中进行。
//executionTimeMillis: 495
//totalKeysExamined: 231689
一旦限制输出条目,则查询效率跟上一个建议出现反转:
不在内存中进行排序的索引查询时间为16ms,匹配的文档为8693条;
在内存中进行排序的时间为495ms,匹配的文档为231689条。
由此可见,如果限制了返回的条目数,则不在内存中进行排序会快得多。
实际应用中,对需要范围查询的数据进行排序一般会取前面的结果,所以推荐使用{"sortKey":1,"queryCriteria":1}这种索引:
把排序键放在索引的前面键,查询条件放在索引的后面键,并且这种索引排序不会在内存中进行,且限制查询条目的时候非常有优势。
当然,如果是需要获取全部数据,使用{"queryCriteria":1,"sortKey":1},这种方式会比较快那么一丢丢,但是注意,这种方式是在内存中进行排序的,注意32MB限制。
一般情况下,推荐使用{"sortKey":1,"queryCriteria":1}索引。
总结
- 单索引:匹配精确条件。
- 多索引:
//有范围条件且需要排序的查询(或许还有输出条目限制),先排序再查询 --> {"sortKey":1,"queryCerteria":1}
db.foo.find({"age":{"$gt":15,"$lt":30}}).sort({"user":-1}).limit(1000).hint({"user":1,"age":1})
//精确条件且需要排序的查询,先查询后排序 --> {"queryCerteria":1,"sortKey":1}
db.foo.find({"age":21}).sort("user":-1).hint({age:1,user:1})
- $nin总是进行全表扫描。
- $or只有在双方的键上都单独创建了索引才会有效率。
- {a:1,b:1.....z:1}以后,{a:1},{a:1,b:1},{a:1,b:1,c:1}等会自动生效,但是子集{b:1},{c:1},{d:1}或{a:1,c:1}等不会生效。
- 取反$ne的效率比较低,因为要进行整个索引条目的扫描,因为除了$ne指定的条目,其他条目一样要全部扫描。