为什么MongoDB在排序后从磁盘获取文档,即使使用的索引包含查询中的所有字段

时间:2018-01-02 17:38:39

标签: database mongodb sorting indexing

显然,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关于如何使用索引返回已排序文档的内容。它指定了如何在复合索引上使用所有前面的字段(在排序字段之前)以便实际使用它。但它没有提及有关索引上的进程字段(排序字段之后)的任何内容。我希望它们可以用于过滤目的而无需点击硬盘。

3 个答案:

答案 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。