MongoDB中$match查询$or语句的性能优化咨询
MongoDB聊天记录查询优化方案
问题背景
在MongoDB的chats集合中执行查询获取指定用户的消息列表时,当用户消息量较大时查询耗时可达10秒。当前使用的简化查询语句及现有索引如下:
简化查询语句
db.chats.explain("executionStats").aggregate([ { "$match": { "$or": [ {"fromid": "******************", "deleted1": false}, {"toid": "******************", "deleted2": false} ] } } ]);
现有索引
"indexName" : "fromid_1_deleted1_1" "indexName" : "toid_1_deleted2_1"
完整聚合查询语句
db.chats.aggregate([ { $match: { $or: [ { "fromid": userID }, { "toid": userID } ], "deleted1": false, "deleted2": false } }, { $sort: { "createdat": -1 } }, { $project: { "fromid": 1, "toid": 1, "createdat": 1, "dateseen": 1, "msg": 1 } }, { $group: { _id: { $cond: { if: { $eq: ["$fromid", userID] }, then: "$toid", else: "$fromid" } }, lastMessage: { $first: "$$ROOT" }, unreadCount: { $sum: { $cond: [ { $and: [ { $ne: ["$fromid", userID] }, { $eq: ["$dateseen", null] } ] }, 1, 0 ] } } } }, { $replaceRoot: { newRoot: { $mergeObjects: ["$lastMessage", { "unSeenMsgCount": "$unreadCount" }] } } }, { $sort: { "createdat": -1 } }, { $skip: offset }, { $limit: limit } ])
优化方案
1. 调整索引适配完整查询逻辑
当前完整查询的$match条件是全局要求deleted1和deleted2均为false,而非原简化查询中每个$or分支单独带删除条件。现有索引无法完全匹配该逻辑,建议创建以下复合索引:
// 适配fromid分支的查询+排序 db.chats.createIndex({ fromid: 1, deleted1: 1, deleted2: 1, createdat: -1 }) // 适配toid分支的查询+排序 db.chats.createIndex({ toid: 1, deleted1: 1, deleted2: 1, createdat: -1 })
- 把
createdat: -1加入索引,可让MongoDB直接从索引获取排序后的结果,避免内存排序(数据量大时内存排序会严重拖慢速度)。 - 索引字段顺序遵循过滤字段在前,排序字段在后的原则,确保索引能同时满足过滤和排序需求。
2. 用$unionWith替代$or提升查询效率
MongoDB 4.4及以上版本支持$unionWith操作符,可将两个独立查询的结果合并,相比$or能更高效地利用索引(每个分支单独匹配对应索引,避免$or可能出现的索引选择问题)。
修改后的聚合查询如下:
db.chats.aggregate([ { $unionWith: { coll: "chats", pipeline: [ // 第一个分支:当前用户作为发送方的消息 { $match: { fromid: userID, deleted1: false, deleted2: false } }, { $sort: { createdat: -1 } }, { $project: { fromid: 1, toid: 1, createdat: 1, dateseen: 1, msg: 1 } } ] } }, { $unionWith: { coll: "chats", pipeline: [ // 第二个分支:当前用户作为接收方的消息 { $match: { toid: userID, deleted1: false, deleted2: false } }, { $sort: { createdat: -1 } }, { $project: { fromid: 1, toid: 1, createdat: 1, dateseen: 1, msg: 1 } } ] } }, // 后续分组、统计逻辑保持不变 { $sort: { "createdat": -1 } }, { $group: { _id: { $cond: { if: { $eq: ["$fromid", userID] }, then: "$toid", else: "$fromid" } }, lastMessage: { $first: "$$ROOT" }, unreadCount: { $sum: { $cond: [ { $and: [ { $ne: ["$fromid", userID] }, { $eq: ["$dateseen", null] } ] }, 1, 0 ] } } } }, { $replaceRoot: { newRoot: { $mergeObjects: ["$lastMessage", { "unSeenMsgCount": "$unreadCount" }] } } }, { $sort: { "createdat": -1 } }, { $skip: offset }, { $limit: limit } ])
3. 聚合流程优化:减少分组前的数据量
由于最终只需要每个对话的最新一条消息和未读计数,可以在每个$unionWith分支中提前过滤出最近的消息(比如最近30天或按数量限制),避免全量数据参与分组计算:
// 在每个$unionWith的pipeline中添加$limit { $unionWith: { coll: "chats", pipeline: [ { $match: { /* 过滤条件 */ } }, { $sort: { createdat: -1 } }, { $limit: 1000 }, // 取每个分支最近1000条消息,根据实际业务调整 { $project: { /* 字段保留 */ } } ] } }
这一步能大幅减少后续分组阶段需要处理的数据量,显著提升查询速度。
内容的提问来源于stack exchange,提问作者Mevo
相关产品推荐
相关产品推荐

