显然,MongoDB从磁盘获取所有文档,以便在排序后能够完成查询。
为了说明这个问题,我们可以在foo集合中插入包含进度和日期的以下文档:
db.foo.insert({ "_id" : 1, "progress" : "A", "date" : ISODate("2017-12-01T07:52:40.831Z")});
db.foo.insert({ "_id" : 2, "progress" : "A", "date" : ISODate("2017-12-02T07:52:40.831Z")});
db.foo.insert({ "_id" : 3, "progress" : "A", "date" : ISODate("2017-12-03T07:52:40.831Z")});
db.foo.insert({ "_id" : 4, "progress" : "B", "date" : ISODate("2017-12-04T07:52:40.831Z")});
db.foo.insert({ "_id" : 5, "progress" : "B", "date" : ISODate("2017-12-05T07:52:40.831Z")});
db.foo.insert({ "_id" : 6, "progress" : "B", "date" : ISODate("2017-12-06T07:52:40.831Z")});
db.foo.insert({ "_id" : 7, "progress" : "C", "date" : ISODate("2017-12-07T07:52:40.831Z")});
db.foo.insert({ "_id" : 8, "progress" : "C", "date" : ISODate("2017-12-08T07:52:40.831Z")});
db.foo.insert({ "_id" : 9, "progress" : "C", "date" : ISODate("2017-12-09T07:52:40.831Z")});
db.foo.insert({ "_id" : 10, "progress" : "D", "date" : ISODate("2017-12-10T07:52:40.831Z")});
db.foo.insert({ "_id" : 11, "progress" : "D", "date" : ISODate("2017-12-11T07:52:40.831Z")});
db.foo.insert({ "_id" : 12, "progress" : "D", "date" : ISODate("2017-12-12T07:52:40.831Z")});
然后创建以下索引:
db.foo.ensureIndex({date : 1, progress:1});
由于日期是索引上的第一个字段,因此可用于按日期排序。索引还包含作为第二个字段的进度。因此,我希望按日期排序的进度查询能够扫描索引,然后过滤仅从磁盘中获取与查询匹配的文档。但是,totalDocsExamined
上的executionStats
显示它已获取整个集合。
这就是我运行explain命令的方式:
db.foo.find({progress:"A"}).sort({ date:-1 }).explain("executionStats")
这就是结果:
{
"queryPlanner" : {
"plannerVersion" : 1.0,
"namespace" : "test.foo",
"indexFilterSet" : false,
"parsedQuery" : {
"progress" : {
"$eq" : "A"
}
},
"winningPlan" : {
"stage" : "FETCH",
"filter" : {
"progress" : {
"$eq" : "A"
}
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"date" : 1.0,
"progress" : 1.0
},
"indexName" : "date_1_progress_1",
"isMultiKey" : false,
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 1.0,
"direction" : "backward",
"indexBounds" : {
"date" : [
"[MaxKey, MinKey]"
],
"progress" : [
"[MaxKey, MinKey]"
]
}
}
},
"rejectedPlans" : [
]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 3.0,
"executionTimeMillis" : 0.0,
"totalKeysExamined" : 12.0,
"totalDocsExamined" : 12.0,
"executionStages" : {
"stage" : "FETCH",
"filter" : {
"progress" : {
"$eq" : "A"
}
},
"nReturned" : 3.0,
"executionTimeMillisEstimate" : 0.0,
"works" : 13.0,
"advanced" : 3.0,
"needTime" : 9.0,
"needYield" : 0.0,
"saveState" : 0.0,
"restoreState" : 0.0,
"isEOF" : 1.0,
"invalidates" : 0.0,
"docsExamined" : 12.0,
"alreadyHasObj" : 0.0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 12.0,
"executionTimeMillisEstimate" : 0.0,
"works" : 13.0,
"advanced" : 12.0,
"needTime" : 0.0,
"needYield" : 0.0,
"saveState" : 0.0,
"restoreState" : 0.0,
"isEOF" : 1.0,
"invalidates" : 0.0,
"keyPattern" : {
"date" : 1.0,
"progress" : 1.0
},
"indexName" : "date_1_progress_1",
"isMultiKey" : false,
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 1.0,
"direction" : "backward",
"indexBounds" : {
"date" : [
"[MaxKey, MinKey]"
],
"progress" : [
"[MaxKey, MinKey]"
]
},
"keysExamined" : 12.0,
"seeks" : 1.0,
"dupsTested" : 0.0,
"dupsDropped" : 0.0,
"seenInvalidated" : 0.0
}
}
},
"serverInfo" : {
"host" : "a98a8d9a4d41",
"port" : 27017.0,
"version" : "3.4.9",
"gitVersion" : "876ebee8c7dd0e2d992f36a848ff4dc50ee6603e"
},
"ok" : 1.0
}
如您所见,totalDocsExamined
为12意味着数据库必须从磁盘中获取它们。该查询使用date_1_progress_1
索引以正确的顺序检索集合,但使用索引的其余部分过滤具有指定进度的文档失败。我希望totalKeysExamined
为12(扫描集合中的所有索引),但totalDocsExamined = nReturned = 3
作为使用的索引包含所有必需的信息。
排序使用索引后,索引的使用方式是否存在限制?
根据我的真实数据,我的收藏品包含数百万份文件。所以排序需要的不仅仅是32 MB sort limit on Mongo。这就是为什么我需要首先使用日期来对数据进行排序,然后通过再次查看索引而不是点击HDD来按进度过滤。
我读了this articule关于如何使用索引返回已排序文档的内容。它指定了如何在复合索引上使用所有前面的字段(在排序字段之前)以便实际使用它。但它没有提及有关索引上的进程字段(排序字段之后)的任何内容。我希望它们可以用于过滤目的而无需点击硬盘。
答案 0 :(得分:0)
限制不是一种排序,它是进入游戏的查询模式。可以找到一个非常详细的解释here
您的索引以Date {Date:1,Progress:1}开头,您的查询将获取进度。当查询模式以定义索引模式的方式开始时,索引工作在查找中。如果在查询中包含日期和进度,您会看到更好的结果,或者您的查询模式顺序部分或完全满足索引(但查询中的字段按索引模式排序)
同样在投影中,您期望文档的所有字段或整个文档与查询条件匹配,并且单独创建的索引无法帮助mongo获取所有信息。
如果您只希望索引满足查询,则应查看documentation的覆盖查询部分。
答案 1 :(得分:0)
这是因为您创建了compound key
如果您在查询中包含两个索引键,则不会发生这种情况。使用以下查询的示例:
db.foo.find({progress:"A", "date" : {$gte: ISODate("2017-12-03T07:52:40.831+0000")}}).explain("executionStats")
它只检查10个文件,而不是12个,因为已经有两个文件:
{
"_id" : 1.0,
"progress" : "A",
"date" : ISODate("2017-12-01T07:52:40.831+0000")
}
{
"_id" : 2.0,
"progress" : "A",
"date" : ISODate("2017-12-02T07:52:40.831+0000")
}
从搜索中排除。
如果您想要此查询:db.foo.find({progress:"A"}).sort({ date:-1 }).explain("executionStats")
只检查3个键,然后你必须改为创建multiple single field indexes:
db.foo.createIndexes([{date : 1}, {progress:1}]);
答案 2 :(得分:0)
显然,在2.6之后引入了这种意外行为的代码更改。
当我尝试计划我的索引时,我试图遵循“平等,排序,范围”规则(在this articule中非常好地解释),但它没有提到平等部分是强制性的(带有目前的mongo版本3.6)
mongo项目中已经有一个ticket来修复它,并允许在排序字段之后的前进字段由索引查看,而不是直接进入缓存/磁盘,即使没有Equality字段在排序字段之前。
至于现在,我们需要确保我们的索引中有一个Equality Field。