提高MongoDb中聚合框架的性能

提高MongoDb中聚合框架的性能,mongodb,Mongodb,我收藏了20万张唱片。我的数据模型如下所示: { "_id" : ObjectId("51750ec159dcef125863b7c4"), "DateAdded" : ISODate("2013-04-22T00:00:00.000Z"), "DateRemoved" : ISODate("2013-12-22T00:00:00.000Z"), "DealerID" : ObjectId("51750bd559dcef07ec964a41"), "ExS

我收藏了20万张唱片。我的数据模型如下所示:

{
    "_id" : ObjectId("51750ec159dcef125863b7c4"),
    "DateAdded" : ISODate("2013-04-22T00:00:00.000Z"),
    "DateRemoved" : ISODate("2013-12-22T00:00:00.000Z"),
    "DealerID" : ObjectId("51750bd559dcef07ec964a41"),
    "ExStockID" : "8324482",
    "Make" : "Mazda",
    "Model" : "3",
    "Price" : 11479,
    "Year" : 2012,
    "Variant" : "1.6d (115) TS 5dr",
    "Turnover": 150
} 
我有几个集合索引,其中一个用于聚合框架的索引是:

{
    "DealerID" : 1,
    "DateRemoved" : -1,
    "Price" : 1,
    "Turnover" : 1
}
正在使用的聚合查询:

db.stats.aggregate([
{
    "$match": {
        "DealerID": {
            "$in": [
                ObjectId("523325ac59dcef1b90a3d446"),
                ....
                // here is specified more than 150 ObjectIds
            ]
        },
        "DateRemoved": {
            "$gte": ISODate("2013-12-01T00:00:00Z"),
            "$lt": ISODate("2014-01-01T00:00:00Z")
        }
    }
},
{ "$project" : { "Price":1, "Turnover":1 } },
{
    "$group": {
        "_id": null,
        "Price": {
            "$avg": "$Price"
        },
        "Turnover": {
            "$avg": "$Turnover"
        }
    }
}]);
执行此查询的时间介于30-200秒之间


如何对此进行优化?

您可以尝试在聚合管道上运行explain,但由于我没有完整的数据集,因此无法正确尝试:

p = [
{
    "$match": {
        "DealerID": {
            "$in": [
                ObjectId("51750bd559dcef07ec964a41"),
                ObjectId("51750bd559dcef07ec964a44"),
            ]
        },
        "DateRemoved": {
            "$gte": ISODate("2013-12-01T00:00:00Z"),
            "$lt": ISODate("2014-01-01T00:00:00Z")
        }
    }
},
{ "$project" : { "Price":1, "Turnover":1 } },
{
    "$group": {
        "_id": null,
        "Price": {
            "$avg": "$Price"
        },
        "Turnover": {
            "$avg": "$Turnover"
        }
    }
}];

db.s.runCommand('aggregate', { pipeline: p, explain: true } );

我建议删除不属于$match(价格和营业额)的字段。此外,我认为您应该切换DealerId和DateRemoved的顺序,因为您希望执行一个范围搜索,然后从该范围中包括所有经销商。相反,这意味着您实际上只能对150个单个项目使用索引,然后您需要进行范围搜索。

您可以尝试在聚合管道上运行explain,但由于我没有完整的数据集,因此无法正确尝试:

p = [
{
    "$match": {
        "DealerID": {
            "$in": [
                ObjectId("51750bd559dcef07ec964a41"),
                ObjectId("51750bd559dcef07ec964a44"),
            ]
        },
        "DateRemoved": {
            "$gte": ISODate("2013-12-01T00:00:00Z"),
            "$lt": ISODate("2014-01-01T00:00:00Z")
        }
    }
},
{ "$project" : { "Price":1, "Turnover":1 } },
{
    "$group": {
        "_id": null,
        "Price": {
            "$avg": "$Price"
        },
        "Turnover": {
            "$avg": "$Turnover"
        }
    }
}];

db.s.runCommand('aggregate', { pipeline: p, explain: true } );

我建议删除不属于$match(价格和营业额)的字段。此外,我认为您应该切换DealerId和DateRemoved的顺序,因为您希望执行一个范围搜索,然后从该范围中包括所有经销商。相反,这意味着您实际上只能对150个单个项目使用索引,然后您需要进行范围搜索。

您可以尝试在聚合管道上运行explain,但由于我没有完整的数据集,因此无法正确尝试:

p = [
{
    "$match": {
        "DealerID": {
            "$in": [
                ObjectId("51750bd559dcef07ec964a41"),
                ObjectId("51750bd559dcef07ec964a44"),
            ]
        },
        "DateRemoved": {
            "$gte": ISODate("2013-12-01T00:00:00Z"),
            "$lt": ISODate("2014-01-01T00:00:00Z")
        }
    }
},
{ "$project" : { "Price":1, "Turnover":1 } },
{
    "$group": {
        "_id": null,
        "Price": {
            "$avg": "$Price"
        },
        "Turnover": {
            "$avg": "$Turnover"
        }
    }
}];

db.s.runCommand('aggregate', { pipeline: p, explain: true } );

我建议删除不属于$match(价格和营业额)的字段。此外,我认为您应该切换DealerId和DateRemoved的顺序,因为您希望执行一个范围搜索,然后从该范围中包括所有经销商。相反,这意味着您实际上只能对150个单个项目使用索引,然后您需要进行范围搜索。

您可以尝试在聚合管道上运行explain,但由于我没有完整的数据集,因此无法正确尝试:

p = [
{
    "$match": {
        "DealerID": {
            "$in": [
                ObjectId("51750bd559dcef07ec964a41"),
                ObjectId("51750bd559dcef07ec964a44"),
            ]
        },
        "DateRemoved": {
            "$gte": ISODate("2013-12-01T00:00:00Z"),
            "$lt": ISODate("2014-01-01T00:00:00Z")
        }
    }
},
{ "$project" : { "Price":1, "Turnover":1 } },
{
    "$group": {
        "_id": null,
        "Price": {
            "$avg": "$Price"
        },
        "Turnover": {
            "$avg": "$Turnover"
        }
    }
}];

db.s.runCommand('aggregate', { pipeline: p, explain: true } );

我建议删除不属于$match(价格和营业额)的字段。此外,我认为您应该切换DealerId和DateRemoved的顺序,因为您希望执行一个范围搜索,然后从该范围中包括所有经销商。相反,这意味着你只能对150个单项使用索引,然后你需要进行范围搜索。

使用@Derick的答案,我找到了阻止创建覆盖索引的索引。据我所见,查询优化器使用第一个索引,它只覆盖查询本身,因此我更改了索引的顺序。这是前后的结果

之前:

{
    "serverPipeline" : [ 
        {
            "query" : {...},
            "projection" : { "Price" : 1, "Turnover" : 1, "_id" : 0 },
            "cursor" : {
                "cursor" : "BtreeCursor DealerIDDateRemoved multi",
                "isMultiKey" : false,
                "n" : 11036,
                "nscannedObjects" : 11008,
                "nscanned" : 11307,
                "nscannedObjectsAllPlans" : 11201,
                "nscannedAllPlans" : 11713,
                "scanAndOrder" : false,
                "indexOnly" : false,
                "nYields" : 0,
                "nChunkSkips" : 0,
                "millis" : 58,
                "indexBounds" : {...},
                "allPlans" : [...],
                "oldPlan" : {...},
                "server" : "..."
            }
        }, 
        {
            "$group" : {...}
        }
    ],
    "ok" : 1
}
在这些更改之后,
indexOnly
param现在显示
true
,这意味着我们刚刚创建了覆盖索引:

{
    "serverPipeline" : [ 
        {
            "query" : {...},
            "projection" : { "Price" : 1, "Turnover" : 1, "_id" : 0 },
            "cursor" : {
                "cursor" : "BtreeCursor DealerIDDateRemovedPriceTurnover multi",
                "isMultiKey" : false,
                "n" : 11036,
                "nscannedObjects" : 0,
                "nscanned" : 11307,
                "nscannedObjectsAllPlans" : 285,
                "nscannedAllPlans" : 11713,
                "scanAndOrder" : false,
                "indexOnly" : true,
                "nYields" : 0,
                "nChunkSkips" : 0,
                "millis" : 58,
                "indexBounds" : {...},
                "allPlans" : [...],
                "server" : "..."
            }
        }, 
        {
            "$group" : {...}
    ],
    "ok" : 1
}

现在,查询大约在0.085-0.300秒之间工作。关于覆盖查询的其他信息

使用@Derick的答案,我找到了阻止创建覆盖索引的索引。据我所见,查询优化器使用第一个索引,它只覆盖查询本身,因此我更改了索引的顺序。这是前后的结果

之前:

{
    "serverPipeline" : [ 
        {
            "query" : {...},
            "projection" : { "Price" : 1, "Turnover" : 1, "_id" : 0 },
            "cursor" : {
                "cursor" : "BtreeCursor DealerIDDateRemoved multi",
                "isMultiKey" : false,
                "n" : 11036,
                "nscannedObjects" : 11008,
                "nscanned" : 11307,
                "nscannedObjectsAllPlans" : 11201,
                "nscannedAllPlans" : 11713,
                "scanAndOrder" : false,
                "indexOnly" : false,
                "nYields" : 0,
                "nChunkSkips" : 0,
                "millis" : 58,
                "indexBounds" : {...},
                "allPlans" : [...],
                "oldPlan" : {...},
                "server" : "..."
            }
        }, 
        {
            "$group" : {...}
        }
    ],
    "ok" : 1
}
在这些更改之后,
indexOnly
param现在显示
true
,这意味着我们刚刚创建了覆盖索引:

{
    "serverPipeline" : [ 
        {
            "query" : {...},
            "projection" : { "Price" : 1, "Turnover" : 1, "_id" : 0 },
            "cursor" : {
                "cursor" : "BtreeCursor DealerIDDateRemovedPriceTurnover multi",
                "isMultiKey" : false,
                "n" : 11036,
                "nscannedObjects" : 0,
                "nscanned" : 11307,
                "nscannedObjectsAllPlans" : 285,
                "nscannedAllPlans" : 11713,
                "scanAndOrder" : false,
                "indexOnly" : true,
                "nYields" : 0,
                "nChunkSkips" : 0,
                "millis" : 58,
                "indexBounds" : {...},
                "allPlans" : [...],
                "server" : "..."
            }
        }, 
        {
            "$group" : {...}
    ],
    "ok" : 1
}

现在,查询大约在0.085-0.300秒之间工作。关于覆盖查询的其他信息

使用@Derick的答案,我找到了阻止创建覆盖索引的索引。据我所见,查询优化器使用第一个索引,它只覆盖查询本身,因此我更改了索引的顺序。这是前后的结果

之前:

{
    "serverPipeline" : [ 
        {
            "query" : {...},
            "projection" : { "Price" : 1, "Turnover" : 1, "_id" : 0 },
            "cursor" : {
                "cursor" : "BtreeCursor DealerIDDateRemoved multi",
                "isMultiKey" : false,
                "n" : 11036,
                "nscannedObjects" : 11008,
                "nscanned" : 11307,
                "nscannedObjectsAllPlans" : 11201,
                "nscannedAllPlans" : 11713,
                "scanAndOrder" : false,
                "indexOnly" : false,
                "nYields" : 0,
                "nChunkSkips" : 0,
                "millis" : 58,
                "indexBounds" : {...},
                "allPlans" : [...],
                "oldPlan" : {...},
                "server" : "..."
            }
        }, 
        {
            "$group" : {...}
        }
    ],
    "ok" : 1
}
在这些更改之后,
indexOnly
param现在显示
true
,这意味着我们刚刚创建了覆盖索引:

{
    "serverPipeline" : [ 
        {
            "query" : {...},
            "projection" : { "Price" : 1, "Turnover" : 1, "_id" : 0 },
            "cursor" : {
                "cursor" : "BtreeCursor DealerIDDateRemovedPriceTurnover multi",
                "isMultiKey" : false,
                "n" : 11036,
                "nscannedObjects" : 0,
                "nscanned" : 11307,
                "nscannedObjectsAllPlans" : 285,
                "nscannedAllPlans" : 11713,
                "scanAndOrder" : false,
                "indexOnly" : true,
                "nYields" : 0,
                "nChunkSkips" : 0,
                "millis" : 58,
                "indexBounds" : {...},
                "allPlans" : [...],
                "server" : "..."
            }
        }, 
        {
            "$group" : {...}
    ],
    "ok" : 1
}

现在,查询大约在0.085-0.300秒之间工作。关于覆盖查询的其他信息

使用@Derick的答案,我找到了阻止创建覆盖索引的索引。据我所见,查询优化器使用第一个索引,它只覆盖查询本身,因此我更改了索引的顺序。这是前后的结果

之前:

{
    "serverPipeline" : [ 
        {
            "query" : {...},
            "projection" : { "Price" : 1, "Turnover" : 1, "_id" : 0 },
            "cursor" : {
                "cursor" : "BtreeCursor DealerIDDateRemoved multi",
                "isMultiKey" : false,
                "n" : 11036,
                "nscannedObjects" : 11008,
                "nscanned" : 11307,
                "nscannedObjectsAllPlans" : 11201,
                "nscannedAllPlans" : 11713,
                "scanAndOrder" : false,
                "indexOnly" : false,
                "nYields" : 0,
                "nChunkSkips" : 0,
                "millis" : 58,
                "indexBounds" : {...},
                "allPlans" : [...],
                "oldPlan" : {...},
                "server" : "..."
            }
        }, 
        {
            "$group" : {...}
        }
    ],
    "ok" : 1
}
在这些更改之后,
indexOnly
param现在显示
true
,这意味着我们刚刚创建了覆盖索引:

{
    "serverPipeline" : [ 
        {
            "query" : {...},
            "projection" : { "Price" : 1, "Turnover" : 1, "_id" : 0 },
            "cursor" : {
                "cursor" : "BtreeCursor DealerIDDateRemovedPriceTurnover multi",
                "isMultiKey" : false,
                "n" : 11036,
                "nscannedObjects" : 0,
                "nscanned" : 11307,
                "nscannedObjectsAllPlans" : 285,
                "nscannedAllPlans" : 11713,
                "scanAndOrder" : false,
                "indexOnly" : true,
                "nYields" : 0,
                "nChunkSkips" : 0,
                "millis" : 58,
                "indexBounds" : {...},
                "allPlans" : [...],
                "server" : "..."
            }
        }, 
        {
            "$group" : {...}
    ],
    "ok" : 1
}


现在,查询大约在0.085-0.300秒之间工作。有关覆盖查询的其他信息

这里只是一个测试,如果删除$avg运算符,速度快吗?很遗憾,您不能在聚合上使用
.explain()
。但是,您可以将$match作为一个普通的
集合来执行。查找
,然后查找
.explain()
,以获得一个非常有用的解释输出,告诉您索引是否有效。$project和$group没有从索引中受益,因此当您特别为这个查询创建索引时,它可能会工作得更好(或者至少也一样好)如果没有
价格
营业额
@Philipp,您实际上可以使用explain,它就无法跨平台工作sharding@Philipp无意争辩,只是想知道为什么$project donesn不能从该指数中获益?看起来Miro试图在这里创建一个覆盖索引$project应该使用索引来避免查找实际文档。如果删除$avg运算符,则此处需要测试。很遗憾,您不能在聚合上使用
.explain()
。但是,您可以将$match作为一个普通的
集合来执行。查找
,然后查找
.explain()
,以获得一个非常有用的解释输出,告诉您索引是否有效。$project和$group没有从索引中受益,因此当您特别为这个查询创建索引时,它可能会工作得更好(或者至少也一样好)没有
价格
营业额