98

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

所以,我总共有 57 个文档,用户想要 10 个文档抵消 20。

我可以想到 2 种方法,首先是查询所有 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});
    });
});
4

7 回答 7

156

我建议您使用 2 个查询:

  1. db.collection.count()将返回项目总数。这个值存储在 Mongo 的某个地方,并且没有计算出来。

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

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

于 2013-03-13T14:02:20.493 回答
29

aggregate()您可以在单个查询中使用,而不是使用 2 个单独的查询:

可以更快地获取聚合“$facet” 、总计数带有跳过和限制的数据

    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/

于 2019-02-15T06:17:39.270 回答
5
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. 并且还可以在查询中根据我们的需要重塑数据。
于 2020-10-15T16:50:13.470 回答
3

在不得不自己解决这个问题之后,我想以 user854301 的回答为基础。

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

以下使用 Bluebird 承诺:

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 }
    }
);

现在计数查询将返回它匹配的总记录,返回的数据将是总记录的子集。

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

于 2018-01-17T00:31:44.173 回答
3

有一个库可以为您完成所有这些工作,请查看mongoose-paginate-v2

于 2019-11-14T16:53:14.740 回答
1

您不必使用两个查询或一个带有聚合等的复杂查询。

您可以使用一个查询

例子:

const getNames = async (queryParams) => {

  const cursor = db.collection.find(queryParams).skip(20).limit(10);
  return {
    count: await cursor.count(),
    data: await cursor.toArray()
  }
  
}

mongo返回一个游标,该游标包含了count等预定义函数,无论skip和limit如何,都会返回查询结果的全数

因此,在 count 属性中,您将获得集合的完整长度,在数据中,您将仅获得偏移量为 20 且限制为 10 个文档的块

于 2021-03-24T09:30:29.487 回答
0

感谢 Igor Igeto Mitkovski,最好的解决方案是使用本地连接

文件在这里:https://docs.mongodb.com/manual/reference/method/cursor.count/#mongodb-method-cursor.count 和猫鼬不支持它(https://github.com/Automattic/mongoose/问题/3283

我们必须使用本机连接。

const query = StudentModel.collection.find(
    {
       age: 13
    }, 
    {
       projection:{ _id:0 }
    }
    ).sort({ time: -1 })
const count = await query.count()
const records = await query.skip(20)
          .limit(10).toArray()
于 2021-10-11T10:19:34.630 回答