【问题标题】:Monogo aggregate query taking too long on serverMongo 聚合查询在服务器上花费的时间太长
【发布时间】:2017-06-23 20:35:39
【问题描述】:

我正在尝试在 mongo 中使用聚合框架来获取一些数据统计信息。我正在使用的查询,在本地运行时几乎不需要一分钟,但是当我在服务器上运行相同的查询时,它没有给出响应,并且在等待太久之后,我不得不取消它,谁能告诉我为什么这发生了。

var orderIds = db.delivery.find({"status":"DELIVERED"}).map(function(o) {
    return o.order 
}); 

var userIds =  db.order.aggregate([{
    $match : { _id : { $in : orderIds } }
}, {
    $group: { _id : "$customer" }
}]).map(function(u) { return u._id });

var userstats = db.order.aggregate([{
    $sort : { customer : 1, dateCreated : 1 }
}, {
    $match : { status : "DELIVERED", customer : {  $in : userIds } }
}, { 
    $group: {
        _id : "$customer", orders : { $sum : 1 }, 
        firstOrderDate : { $first : "$dateCreated" },
        lastOrderDate : { $last : "$dateCreated" }
    }
}]);

userstats.forEach(function(x) { 
    db.user.update({ _id : x._id }, {
        $set : { 
            totalOrders : x.orders,
            firstOrderDate : x.firstOrderDate,
            lastOrderDate : x.lastOrderDate
        }
    })
})

我不确定,但它在服务器上不应该更快吗? ,但它无法提供输出。

【问题讨论】:

    标签: node.js mongodb aggregation-framework


    【解决方案1】:

    为了加快流程,您可以通过多种方式重构您的操作。 首先是消除不必要的管道操作,例如 $sort 操作符,可以用 $max$min 操作符代替在 $group 管道中。

    其次,使用 bulk() API 这将提高更新操作的性能,尤其是在处理大型集合时,因为它们会将操作分批发送到服务器(例如,批量大小为 500)不同于将每个请求发送到服务器(就像您当前在 forEach() 循环中使用更新语句所做的那样)。

    考虑以下重构操作:

    var orderIds = db.delivery.find({"status": "DELIVERED"}).map(function(d){return d.order;}),
        counter = 0,
        bulk = db.user.initializeUnorderedBulkOp();
    
    var userstatsCursor = db.orders.aggregate([
        { "$match": { "_id": { "$in": orderIds } } },
        { 
            "$group": { 
                "_id": "$customer", 
                "orders": { "$sum": 1 },
                "firstOrderDate": { "$min": "$dateCreated" },
                "lastOrderDate":{ "$max": "$dateCreated" } } 
            } 
        }
    ]);
    
    userstatsCursor.forEach(function (x){
        bulk.find({ "_id": x._id }).updateOne({ 
            "$set": { 
                "totalOrders": x.orders,
                "firstOrderDate": x.firstOrderDate,
                "lastOrderDate": x.lastOrderDate
            }
        });
    
        counter++;
        if (counter % 500 == 0) {
            bulk.execute(); // Execute per 500 operations and 
            // re-initialize every 500 update statements
            bulk = db.user.initializeUnorderedBulkOp();
        }
    });
    
    // Clean up remaining operations in queue
    if (counter % 500 != 0) { bulk.execute(); }
    

    【讨论】:

    • 解决方案看起来不错,我可以看到很多新东西,但是查询以某种方式抛出语法错误“Unexpected token }”
    • @user29578 有一个错字,我在更新的答案中删除了管道数组之前的一个额外的右括号。再试一次。
    • 嗨,chridam,我收到此错误 2016-02-14T02:32:33.615+0530 TypeError: Object function () { return new Bulk(this, false);在 (shell):1:436 在 Object.DBQuery.forEach (src/mongo/shell/query.js:281:9) 在 (shell):1:409 处没有方法“查找”
    • @user29578 您使用的是哪个 MongoDB 版本,您尝试了哪种方法?
    • 我正在使用 2.7.2,我正在尝试从您的答案中运行相同的查询,但它在批量查找方法时抛出此错误,不知道为什么
    【解决方案2】:

    我建议您将 $match 设置为管道中的第一个操作,因为 $match 运算符只能在聚合管道中位于第一个时使用索引:

    var userstats = db.order.aggregate([{
        $match : {
            status :"DELIVERED", 
            customer : { $in : userIds }
        }
    }, {
        $sort : {
            customer : 1,
            dateCreated : 1
        }
    }, { 
        $group : {  
            _id : "$customer",
            orders : { $sum : 1 }, 
            firstOrderDate: { $first : "$dateCreated" },
            lastOrderDate : { $last:"$dateCreated" }
        }
    }]);
    

    如果您还没有定义一个索引,您还应该在 statuscustomer 上添加一个索引:

    db.delivery.createIndex({status:1,customer:1})
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-10-05
    • 1970-01-01
    • 2014-05-22
    • 2020-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多