如何优化这段MongoDB聚合查询以提升响应速度?
MongoDB聚合查询优化方案
针对你的聚合查询性能问题,以下是具体的优化思路和修改后的代码:
1. 前置过滤,缩小数据集范围
原查询先拉取一周内所有search_history数据再关联过滤,会处理大量无关数据。建议先从positive表筛选出目标tag对应的search_id,再用这些search_id过滤search_history,从源头减少数据量。
2. 优化聚合管道顺序与Lookup逻辑
将match过滤提前,调整Lookup的方式,避免不必要的数据展开和合并:
- 把
positive表的过滤逻辑前置,直接获取目标search_id列表 - 简化用户信息Lookup的字段提取,避免后续的
unwind和replaceRoot操作
3. 聚合阶段完成去重,避免内存过滤
原代码在toArray()后用JS数组过滤去重,数据量大时性能极差。改用聚合的$group按url分组,保留最新的记录。
修改后的完整代码
async getHistoryByTag(context: StandardContext) { const { mongo } = context.state; const tag = context.params.tag.replace(/-+/g, " "); const today = new Date(); const prevSunday = getOneWeekAgo(today); console.log("today: ", today, "prev sunday: ", prevSunday); // 先获取目标tag对应的search_id,缩小后续查询范围 const targetSearchIds = await mongo.collection("positive") .find({ word: tag }, { search_id: 1, _id: 0 }) .map(doc => doc.search_id) .toArray(); if (targetSearchIds.length === 0) { context.response.body = []; return; } const all = await mongo .collection("search_history") .aggregate<SearchHistoryModel & { username: string; word: string }>([ // 同时过滤时间和search_id,大幅减少初始数据集 { $match: { created_at: { $gt: prevSunday.toISOString() }, search_id: { $in: targetSearchIds } }, }, // Lookup用户信息,直接提取username { $lookup: { from: "users", localField: "user_id", foreignField: "id", as: "username", pipeline: [ { $project: { username: 1, _id: 0 } }, { $replaceRoot: { newRoot: "$username" } } ], }, }, // 提取username字段,避免unwind操作 { $addFields: { username: { $first: "$username" } } }, // 按url分组,保留最新的记录 { $group: { _id: "$url", search_id: { $first: "$search_id" }, user_id: { $first: "$user_id" }, created_at: { $max: "$created_at" }, username: { $first: "$username" }, word: { $first: tag } } }, // 整理字段结构 { $project: { _id: 0, url: "$_id", search_id: 1, user_id: 1, created_at: 1, username: 1, word: 1 } }, // 按创建时间倒序 { $sort: { created_at: -1 } } ]) .toArray(); context.response.body = all; }
4. 索引优化建议
确保以下索引已创建,让每个过滤阶段都能命中索引:
search_history集合:{ created_at: 1, search_id: 1 }复合索引(同时满足时间和search_id过滤)positive集合:{ word: 1, search_id: 1 }复合索引(快速匹配tag并返回search_id)users集合:{ id: 1, username: 1 }复合索引(加速Lookup时的查询和投影)
其他优化点
- 避免在聚合中使用
$unwind+$replaceRoot这类会膨胀数据的操作,尽量用$addFields或$project直接提取字段 - 如果
search_history数据量极大,可以考虑按时间分片,进一步缩小查询范围
内容的提问来源于stack exchange,提问作者chovy
相关产品推荐
相关产品推荐

