Mongoose操作MongoDB如何单次聚合返回计数与分页文档
问题根因
当前触发MongoDB索引扫描占比告警的直接原因是重复查询:countDocuments 统计总数、aggregate 拉取分页数据两个请求使用了完全一致的匹配条件,会触发两次相同范围的索引扫描,相当于同样的扫描工作量翻倍执行。随着视频集合数据量涨到当前的2-3倍,除了告警之外,还会快速占用数据库CPU、内存资源,导致查询响应时间陡增。
你之前调研的$count确实只能单独返回计数,无法同时返回文档,但聚合框架提供的$facet阶段可以完美解决这个问题:它支持在同一次聚合请求中并行运行多个独立子管道,且所有子管道共享前置$match阶段的索引扫描结果,不会重复扫描数据。
优化实现
直接把两次查询合并为单次聚合请求,代码如下:
const [queryResult] = await videoModel.aggregate([ { // 匹配条件只写一次,仅执行一次索引扫描 $match: { ...sortBy, $or: [ { videoTitle: { $regex: keyword, $options: "i", }, }, ], }, }, { $facet: { // 子管道1:统计匹配总数 totalCount: [ { $count: "count" } ], // 子管道2:执行排序、分页,返回当前页数据 videoList: [ { $sort: { createdAt: -1 } }, { $skip: pageSize * (page - 1) }, { $limit: pageSize } ] } } ]); // 解析结果,无匹配数据时总数默认为0 const count = queryResult.totalCount[0]?.count || 0; const videos = queryResult.videoList;
额外优化建议
- 目前使用的无左锚点、大小写不敏感的正则查询无法命中普通B树索引,数据量继续上涨后会出现全集合扫描问题,建议给
videoTitle字段创建文本索引,改用$text操作符做检索,可大幅降低索引扫描量。 - 如果存在页码较大的深分页场景,
$skip会随页码增大线性降低性能,可替换为基于上一页最后一条文档_id/createdAt的游标分页,查询性能不会随页码增长衰减。
内容的提问来源于stack exchange,提问作者Austin Howard
相关产品推荐
相关产品推荐

