如何通过Mongoose在MongoDB中同时查询文档并统计总数
解决方案:用MongoDB聚合管道合并分页查询与总数统计
你当前的问题在于两次重复执行相同过滤逻辑的数据库查询,数据量较大时会产生不必要的性能损耗。可以通过MongoDB的聚合管道,借助$facet阶段在单次查询中同时获取分页数据和符合条件的总文档数,彻底避免重复计算。
优化后的完整代码
const getAllBooks = asyncHandler(async (req, res) => { // 处理查询参数,修正类型与格式问题 const page = parseInt(req.query.page) || 1; const limit = 5; const skip = (page - 1) * limit; const allGenres = ["action", "drama", "sci-fi", "romance", "comedy"]; const reqGenre = req.query.genre === "" ? allGenres : req.query.genre.split(",").map(g => g.trim()); const search = req.query.search || ""; // 构建搜索过滤条件 const searchBy = req.query.searchBy === "title" ? { title: { $regex: search, $options: "i" } } : { author: { $regex: search, $options: "i" } }; // 构建排序规则 const sortDir = req.query.sort === "asc" ? 1 : -1; const sortBy = req.query.sortBy === "author" ? { author: sortDir } : { title: sortDir }; // 单次聚合查询同时获取分页数据与总数 const aggregateResult = await Book.aggregate([ // 应用搜索过滤 { $match: searchBy }, // 过滤指定分类的书籍 { $match: { genre: { $in: reqGenre } } }, // 执行排序 { $sort: sortBy }, // 用$facet拆分两个处理分支 { $facet: { // 分支1:获取当前页的书籍数据 books: [ { $skip: skip }, { $limit: limit }, { $project: { createdAt: 0, updatedAt: 0, __v: 0 } } // 排除冗余字段 ], // 分支2:统计符合条件的总文档数 totalCount: [ { $count: "value" } ] } }, // 提取总数的具体数值(从数组结构转为单个数字) { $addFields: { totalCount: { $arrayElemAt: ["$totalCount.value", 0] } } } ]); // 解析聚合结果 const { books, totalCount } = aggregateResult[0]; const totalPages = Math.ceil(totalCount / limit); // 修正总页数计算逻辑 res.status(200).json({ books, totalCount, totalPages }); });
核心优化说明
$facet阶段:允许在同一聚合管道内同时执行多个独立操作分支,这里一个分支处理分页(skip+limit),另一个分支用$count统计总数,只需要执行一次过滤、排序逻辑,大幅减少数据库IO开销。- 参数处理优化:将
page转为整数避免字符串计算错误;给reqGenre添加trim()处理,防止传入带空格的无效分类值;简化排序条件的判断逻辑,提升代码可读性。 - 总页数修正:原代码用
documents / limit会得到小数,改用Math.ceil()得到符合实际分页逻辑的总页数(比如11条数据、每页5条时,总页数应为3)。
额外性能建议
如果模糊搜索是高频场景,建议给title和author字段创建文本索引,替代正则表达式匹配,进一步提升搜索性能:
// 在你的LibrarySchema中添加文本索引 LibrarySchema.index({ title: "text", author: "text" });
之后可将搜索条件改为文本搜索格式:
const searchBy = search ? { $text: { $search: search } } : {};
内容的提问来源于stack exchange,提问作者Roshan Shetty
相关产品推荐
相关产品推荐

