猫鼬限制/偏移量和计数查询


84

查询性能有点奇怪...我需要运行一个查询,该查询可以对文档总数进行计数,并且还可以返回可以限制和偏移的结果集。

因此,我总共有57个文档,而用户希望将10个文档偏移20。

我可以想到两种方法,首先是查询所有57个文档(作为数组返回),然后使用array.slice返回所需的文档。第二个选项是运行2个查询,第一个查询使用mongo的本地“ count”方法,然后使用mongo的本地$ limit和$ skip聚合器运行第二个查询。

您认为哪种方法更好?在一个查询中执行全部操作,还是运行两个单独的查询?

编辑:

// 1 query
var limit = 10;
var offset = 20;

Animals.find({}, function (err, animals) {
    if (err) {
        return next(err);
    }

    res.send({count: animals.length, animals: animals.slice(offset, limit + offset)});
});


// 2 queries
Animals.find({}, {limit:10, skip:20} function (err, animals) {            
    if (err) {
        return next(err);
    }

    Animals.count({}, function (err, count) {
        if (err) {
            return next(err);
        }

        res.send({count: count, animals: animals});
    });
});

我不确定Mongoose,但是count()PHP不会默认limit或不考虑默认函数,skip除非告知这样做,否则只运行一个查询limit和skip然后获取计数应该会在这里提供最有效的解决方案。但是,如果您不进行两次查询以计算当前的内容,那么您将如何知道有57个文档呢?您是否有一个永不改变的静态数字?如果不是,那么您将需要同时进行跳过和限制,然后再进行计数。
Sammaye 2012年

抱歉,我正在谈论使用Mongo的本机计数方法db.collection.find(<query>).count();
leepowell 2012年

抱歉是我,我听错了你的问题。嗯,实际上我不确定哪个会更好,您的结果集会像57个文档一样总是很低吗?如果是这样,那么客户端切片的性能可能会提高一毫秒。
Sammaye

我在原始问题中添加了示例,我认为数据不会高达10,000+,但有可能。
leepowell 2012年

在10k记录下,您可能会发现JS的内存处理性能不如count()MongoDB的功能。count()MongoDB中的功能相对较慢,但它仍然与大多数大型集上的客户端变体一样快,并且可能比此处客户端计数更快。但是,这部分内容取决于您自己的测试。请注意,我之前很容易就已经数过10k长度的数组,因此它可能是更快的客户端,这在10k元素上很难说。
Sammaye 2012年

Answers:


129

我建议您使用2个查询:

  1. db.collection.count()将返回项目总数。此值存储在Mongo中的某个位置,并且不会计算。

  2. db.collection.find().skip(20).limit(10)在这里,我假定您可以使用按某个字段排序,所以不要忘记在此字段上添加索引。此查询也将很快。

我认为您不应该查询所有项目,而应该执行跳过和获取,因为稍后在您拥有大数据时,您将在数据传输和处理方面遇到问题。


1
我写的只是一条注释,没有任何保留,但是我听说该.skip()指令对于CPU来说很繁琐,因为它转到集合的开头并达到的参数中指定的值.skip()。它可以对大量收藏产生真正的影响!但是.skip()无论如何我都不知道在使用之间或者在使用JS整理整个收藏夹中哪一个是最重的。
Zachary Dahan

2
@Stuffix我听说过使用相同的担忧.skip()。这个答案可以解决,并建议在日期字段上使用过滤器。可以将其与.skip().take()方法一起使用。这似乎是个好主意。但是,我在处理此OP的问题时遇到了麻烦,该问题是如何获取总数的文档。如果使用过滤器来消除的性能影响.skip(),我们如何才能获得准确的计数?存储在数据库中的计数不会反映我们过滤后的数据集。
Michael Leanos '16

嗨@MichaelLeanos,我也面临着同样的问题:即如何获取总文档数。如果使用过滤器,那么我们如何才能得到准确的计数?您有解决方案吗?
virsha'2

@virsha,用于cursor.count()返回已过滤的documnets的数量(它将不执行查询,而是将返回匹配文档的数量)。确保对过滤器和订单属性进行索引,并且一切都会好起来。
user854301 '17

@virshacursor.count()应该按照@ user854301的指示使用。但是,我最终要做的是在我的API(/api/my-colllection/stats)中添加了一个端点,该端点用于使用Mongoose的db.collection.stats功能返回集合中的各种统计信息。由于我确实只需要前端使用此功能,因此我只是查询端点以独立于服务器端分页来返回该信息。
Michael Leanos '02

19

可以使用aggregate()一个查询来代替使用2个单独的查询:

总结“$面”可以更快地获取的总数具有跳跃和限制数据

    db.collection.aggregate([

      //{$sort: {...}}

      //{$match:{...}}

      {$facet:{

        "stage1" : [ {"$group": {_id:null, count:{$sum:1}}} ],

        "stage2" : [ { "$skip": 0}, {"$limit": 2} ]
  
      }},
     
     {$unwind: "$stage1"},
  
      //output projection
     {$project:{
        count: "$stage1.count",
        data: "$stage2"
     }}

 ]);

输出如下:

[{
     count: 50,
     data: [
        {...},
        {...}
      ]
 }]

另外,请查看https://docs.mongodb.com/manual/reference/operator/aggregation/facet/


2

在必须自己解决此问题之后,我想以user854301的答案为基础。

猫鼬^ 4.13.8我能够使用一个名为 toConstructor(),使我避免在应用过滤器时多次构建查询。我知道此功能在较早的版本中也可用,但是您必须检查Mongoose文档以确认这一点。

以下使用Bluebird Promise:

let schema = Query.find({ name: 'bloggs', age: { $gt: 30 } });

// save the query as a 'template'
let query = schema.toConstructor();

return Promise.join(
    schema.count().exec(),
    query().limit(limit).skip(skip).exec(),

    function (total, data) {
        return { data: data, total: total }
    }
);

现在,count查询将返回其匹配的总记录,并且返回的数据将是总记录的子集。

请注意构造查询的query ()周围的()。



0
db.collection_name.aggregate([
    { '$match'    : { } },
    { '$sort'     : { '_id' : -1 } },
    { '$facet'    : {
        metadata: [ { $count: "total" } ],
        data: [ { $skip: 1 }, { $limit: 10 },{ '$project' : {"_id":0} } ] // add projection here wish you re-shape the docs
    } }
] )

而不是使用两个查询来查找总数并跳过匹配的记录。
$ facet是最佳和优化的方法。

  1. 匹配记录
  2. 查找total_count
  3. 跳过记录
  4. 并且还可以根据查询中的需要重塑数据。

1
请在回答中添加一些解释,以便其他人可以从中学习
Nico Haase
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.