You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 20:30:54