如何优化MongoDB聚合查询?需将慢查询提速至300ms以内
MongoDB聚合查询性能优化方案
问题描述
我拥有users和attendances两个集合,当前通过MongoDB聚合查询中的$lookup关联两个集合获取用户考勤数据,随后使用$filter筛选符合条件的考勤数据,再通过$reduce精简数据结构。但该查询在本地(150mbps网络环境)执行时,仅查询10条用户数据(总用户数为1000)却耗时1分钟,希望将查询耗时优化至300ms以内。
当前使用的聚合查询代码:
const attendanceData = await User.aggregate([ { $match: { lastLocationId: Mongoose.Types.ObjectId(typeId), isActive: true, }, }, { $project: { _id: 1, workerId: 1, workerFirstName: 1, workerSurname: 1, }, }, { $lookup: { from: "attendances", localField: "_id", foreignField: "employeeId", as: "attendances", }, }, { $set: { attendances: { $filter: { input: "$attendances", cond: { $and: [ { $gte: ["$$this.Date", new Date(fromDate)], }, { $lte: ["$$this.Date", new Date(toDate)], }, { $eq: ["$$this.createdAs", dataType], }, { $eq: ["$$this.status", true], }, { $eq: ["$$this.workerType", workerType], }, ], }, }, }, }, }, { $set: { attendances: { $reduce: { input: "$attendances", initialValue: [], in: { $concatArrays: [ "$$value", [ { _id: "$$this._id", createdAs: "$$this.createdAs", Date: "$$this.Date", }, ], ], }, }, }, }, }, { $skip: 0 }, { $limit: 10 }, ]);
优化方案
1. 添加针对性复合索引
索引是提升MongoDB查询性能的核心,针对当前查询的过滤和关联逻辑,创建以下索引:
- users集合索引:加速
$match阶段的用户筛选db.users.createIndex({ lastLocationId: 1, isActive: 1 }) - attendances集合索引:加速关联查询和考勤数据筛选
db.attendances.createIndex({ employeeId: 1, Date: 1, createdAs: 1, status: 1, workerType: 1 })
2. 重构聚合流程,减少无效数据处理
原查询的问题在于:先筛选所有符合条件的用户,再全量关联他们的考勤数据,最后才取10条结果,导致大量无效数据被加载和处理。优化思路是提前限制用户数量,并在$lookup阶段直接完成考勤数据的筛选和字段精简:
优化后的聚合代码:
const attendanceData = await User.aggregate([ // 第一步:筛选目标用户 { $match: { lastLocationId: Mongoose.Types.ObjectId(typeId), isActive: true, }, }, // 第二步:先取需要的10条用户数据,减少后续关联量 { $skip: 0 }, { $limit: 10 }, // 第三步:仅保留需要的用户字段 { $project: { _id: 1, workerId: 1, workerFirstName: 1, workerSurname: 1, }, }, // 第四步:使用带pipeline的$lookup,在关联阶段完成考勤数据的筛选和字段投影 { $lookup: { from: "attendances", let: { userId: "$_id" }, pipeline: [ { $match: { $expr: { $and: [ { $eq: ["$employeeId", "$$userId"] }, { $gte: ["$Date", new Date(fromDate)] }, { $lte: ["$Date", new Date(toDate)] }, { $eq: ["$createdAs", dataType] }, { $eq: ["$status", true] }, { $eq: ["$workerType", workerType] }, ], }, }, }, // 直接投影需要的字段,替代原有的$reduce逻辑 { $project: { _id: 1, createdAs: 1, Date: 1, }, }, ], as: "attendances", }, }, ]);
3. 优化点说明
- 提前$skip/$limit:将分页操作移到
$match之后,只对10个用户进行关联查询,避免处理大量无关用户的考勤数据。 - 带pipeline的$lookup:在关联考勤集合时直接筛选符合条件的记录,并仅保留需要的字段,无需先加载全量数据再过滤,大幅减少数据传输和内存占用。
- 移除冗余的$reduce:原
$reduce逻辑仅用于重组数组结构,在$lookup的pipeline中用$project即可直接实现,简化计算流程。
内容的提问来源于stack exchange,提问作者chirag prajapati
相关产品推荐
相关产品推荐

