mongodb中大量记录分页缓慢

kd3sttzy  于 2023-02-03  发布在  Go
关注(0)|答案(9)|浏览(406)

我在蒙戈的一个收藏中有超过30万张唱片。
当我运行这个非常简单的查询时:

db.myCollection.find().limit(5);

只需要几毫秒。
但是当我在查询中使用skip时:

db.myCollection.find().skip(200000).limit(5)

它不会返回任何东西...它运行几分钟,什么也不返回。
如何让它变得更好?

eanckbw9

eanckbw91#

解决这个问题的一个方法是,如果您有大量文档,并且您要以 sorted 顺序显示它们(如果您不这样做,我不确定skip是否有用),则使用排序所依据的键来选择下一页结果。
如果你从

db.myCollection.find().limit(100).sort({created_date:true});

然后将游标返回的 last 文档的创建日期提取到变量max_created_date_from_last_result中,则可以使用效率高得多的查询(假设在created_date上有一个索引)获取下一页

db.myCollection.find({created_date : { $gt : max_created_date_from_last_result } }).limit(100).sort({created_date:true});
vc6uscn9

vc6uscn92#

来自MongoDB documentation

寻呼成本

遗憾的是,skip可能(非常)昂贵,并且要求服务器从集合或索引的开始处遍历到偏移/skip位置,然后才能开始返回数据页(限制)。随着页数的增加,skip将变得更慢,CPU密集度更高,并且可能在较大的集合中受到IO限制。
基于范围的分页可以更好地使用索引,但不允许您轻松地跳转到特定页。
你得问自己一个问题:多久需要40000页?也可以参考this文章;

dzjeubhm

dzjeubhm3#

我发现把这两个概念结合在一起(skip+limit和find+limit)性能很好。skip+limit的问题是当你有很多文档(特别是大文档)时性能很差。find+limit的问题是你不能跳转到任意页面。我希望能够不按顺序分页。
我采取的步骤是:
1.根据您希望如何对文档进行排序来创建索引,或者只使用default _id索引(这是我使用的)
1.知道起始值、页面大小和要跳转到的页面

  1. Project + skip +限制起始值
    1.查找+限制页面结果
    如果我想得到第5432页的16条记录(用javascript编写),大致如下所示:
let page = 5432;
let page_size = 16;
let skip_size = page * page_size;

let retval = await db.collection(...).find().sort({ "_id": 1 }).project({ "_id": 1 }).skip(skip_size).limit(1).toArray();
let start_id = retval[0].id;

retval = await db.collection(...).find({ "_id": { "$gte": new mongo.ObjectID(start_id) } }).sort({ "_id": 1 }).project(...).limit(page_size).toArray();

这是因为即使跳过数百万条记录,跳过投影索引的速度也非常快(这就是我正在做的)如果您运行explain("executionStats"),它仍然有一个很大的totalDocsExamined数,但由于在索引上的投影,它非常快(本质上,数据blob永远不会被检查)。然后,有了页面开始的值在手,就可以非常快地获取下一页。

yyyllmsg

yyyllmsg4#

我把两个答案连在一起。
问题是当你使用跳过和限制,没有排序,它只是分页的顺序表在相同的顺序,因为你写数据到表,所以引擎需要作出第一个临时索引。是更好地使用ready _id索引:)你需要使用排序by _id。

db.myCollection.find().skip(4000000).limit(1).sort({ "_id": 1 });

在PHP中它将是

$manager = new \MongoDB\Driver\Manager("mongodb://localhost:27017", []);
$options = [
            'sort' => array('_id' => 1),
            'limit' => $limit, 
            'skip' => $skip,

        ];
$where = [];
$query = new \MongoDB\Driver\Query($where, $options );
$get = $manager->executeQuery("namedb.namecollection", $query);
k3fezbri

k3fezbri5#

我将建议一个更激进的方法。合并跳过/限制(实际上是一种边缘情况),使用基于范围的存储桶进行排序,页面不是基于固定数量的文档,而是基于一个时间范围(或者你的排序)。所以你有每个时间范围的顶级页面,如果你需要跳过/限制,你有那个时间范围内的子页面,但是我怀疑可以把bucket做得足够小,根本不需要跳过/限制。通过使用排序索引,这可以避免光标遍历整个库存到达最后一页。

xxls0lw8

xxls0lw86#

我的收藏中有大约130万个文档(不是那么大),正确地索引,但仍然受到这个问题的严重影响。
阅读其他答案后,向前的解决方案是明确的;分页的集合必须通过类似于SQL的自动递增值的计数整数而不是基于时间的值来排序。
问题出在skip上;没有其他办法可以绕过它;如果您使用skip,那么当您的集合增长时,您一定会遇到这个问题。
使用带索引的计数整数允许使用索引跳转而不是跳过。这不适用于基于时间的值,因为您无法计算基于时间的跳转位置,因此跳过是后一种情况下的唯一选项。
另一方面,
通过为每个文档分配计数,写入性能将受到影响;因为所有文档都必须按顺序插入。这对我的用例来说没问题,但我知道这个解决方案并不适合所有人。
得票最多的答案似乎不适用于我的情况,但这个答案适用(我需要能够通过任意页码向前查找,而不是一次一页)。
另外,如果你正在处理delete,这也很难,但仍然有可能,因为MongoDB支持$inc与一个负值批量更新.幸运的是,我不必处理删除在我维护的应用程序.
把这个写下来,作为对未来的自己的一个提醒。用我现在正在处理的应用程序解决这个问题可能太麻烦了,但是下一次,如果我遇到类似的情况,我会构建一个更好的应用程序。

3hvapo4f

3hvapo4f7#

如果你有mongos的默认id是ObjectId,那就用它来代替,这可能是大多数项目最可行的选择。
the official mongo docs所述:
skip()方法要求服务器在开始返回结果之前从输入结果集的开头开始扫描。随着偏移量的增加,skip()将变慢。
范围查询可以使用索引来避免扫描不需要的文档,与使用skip()进行分页相比,随着偏移量的增加,范围查询通常会产生更好的性能。
降序(示例):

function printStudents(startValue, nPerPage) {
  let endValue = null;
  db.students.find( { _id: { $lt: startValue } } )
             .sort( { _id: -1 } )
             .limit( nPerPage )
             .forEach( student => {
               print( student.name );
               endValue = student._id;
             } );
  return endValue;
}

这里是升序示例。

qlckcl4x

qlckcl4x8#

如果知道要限制的元素的ID

db.myCollection.find({_id: {$gt: id}}).limit(5)

这是一个很棒的解决方案,很有魅力

c2e8gylq

c2e8gylq9#

为了更快地分页,不要使用skip()函数,而要使用limit()和find()来查询前一页的最后一个id。
这里是一个例子,我正在查询超过吨的文档使用spring Boot :

Long totalElements = mongockTemplate.count(new Query(),"product");
    int page =0;
    Long pageSize = 20L;
    String lastId = "5f71a7fe1b961449094a30aa"; //this is the last if of the precedent page

    for(int i=0; i<(totalElements/pageSize); i++){
        page +=1;
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.match(Criteria.where("_id").gt(new ObjectId(lastId))),
            Aggregation.sort(Sort.Direction.ASC,"_id"),
            new CustomAggregationOperation(queryOffersByProduct),
            Aggregation.limit((long)pageSize)
        );

    List<ProductGroupedOfferDTO> productGroupedOfferDTOS = mongockTemplate.aggregate(aggregation,"product",ProductGroupedOfferDTO.class).getMappedResults();

    lastId = productGroupedOfferDTOS.get(productGroupedOfferDTOS.size()-1).getId();

}

相关问题