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

MongoDB聚合查询优化:屏蔽用户排除的未读消息统计提速方案

MongoDB 聚合查询优化:排除屏蔽用户的未读消息统计

问题背景

我有两个MongoDB集合:

  • conversations2:存储聊天会话数据,每条记录对应一个用户与其他用户的对话,包含unreadMessageCount(未读消息数)字段
  • user_blocked:存储系统中被屏蔽的用户ID

需求是统计指定用户(如userId=32)的未读消息总数,但需要排除对方(otherUserId)属于屏蔽列表的会话记录。当前使用的聚合查询能实现需求,但针对消息量大的用户(比如该用户有45万条会话记录)查询速度过慢。已为conversations2.userId、conversations2.otherUserId和user_blocked.id单独创建索引,现寻求优化方案。

当前查询代码

db.conversations2.aggregate([
  {
    $match: {
      userId: 32
    }
  },
  {
    $lookup: {
      from: "user_blocked",
      localField: "otherUserId",
      foreignField: "id",
      as: "blockedUsers"
    }
  },
  {
    $match: {
      blockedUsers: {
        $eq: []
      }
    }
  },
  {
    $group: {
      _id: "$userId",
      unreadMessageCount: {
        $sum: "$unreadMessageCount"
      }
    }
  }
])

集合示例数据

conversations2

{
  "_id": { "$oid": "65c0f64030054c4b8f0481a0" },
  "otherUserId": { "$numberLong": "45" },
  "userId": { "$numberLong": "32" },
  "lastMessage": "test",
  "lastMessageTime": { "$date": "2024-02-21T10:36:44.592Z" },
  "lastMessageType": 1,
  "lastMessageWay": "in",
  "unreadMessageCount": 29
}

user_blocked

{
  "_id": { "$oid": "66033f989bba279fe7d0862a" },
  "id": { "$numberLong": "45" }
}

原查询性能瓶颈分析

原查询的核心问题是:先通过$match过滤出目标用户的所有会话(45万条),然后对每条记录单独执行$lookup关联user_blocked集合。这种逐条关联的方式会触发大量的单条查询,即使有索引,也会产生极高的IO开销,导致整体速度缓慢。

优化方案

方案1:预取屏蔽列表,用$nin直接过滤(推荐)

先一次性获取所有被屏蔽的用户ID,再在$match阶段直接排除这些ID对应的会话,避免逐条关联操作。

// 第一步:获取所有被屏蔽的用户ID数组
const blockedUserIds = db.user_blocked.find({}, { id: 1, _id: 0 }).map(doc => doc.id);

// 第二步:聚合统计未读消息数
db.conversations2.aggregate([
  {
    $match: {
      userId: 32,
      otherUserId: { $nin: blockedUserIds }
    }
  },
  {
    $group: {
      _id: "$userId",
      unreadMessageCount: { $sum: "$unreadMessageCount" }
    }
  }
])

优化点:

  • 把原来的N次关联查询变成1次查询+批量过滤,大幅减少IO操作
  • conversations2上的userId和otherUserId索引可以被$match阶段高效利用,快速过滤出符合条件的记录

方案2:优化$lookup的关联逻辑

如果不想拆分两次查询,可以使用$lookup的子管道功能,在关联阶段就终止不必要的查询,减少数据传输量。

db.conversations2.aggregate([
  {
    $match: {
      userId: 32
    }
  },
  {
    $lookup: {
      from: "user_blocked",
      let: { targetOtherId: "$otherUserId" },
      pipeline: [
        { $match: { $expr: { $eq: ["$id", "$$targetOtherId"] } } },
        { $limit: 1 } // 匹配到第一条就停止查询,减少数据返回
      ],
      as: "blockedUsers"
    }
  },
  {
    $match: {
      blockedUsers: { $size: 0 } // 用$size判断空数组,比$eq: []性能更优
    }
  },
  {
    $group: {
      _id: "$userId",
      unreadMessageCount: { $sum: "$unreadMessageCount" }
    }
  }
])

优化点:

  • 子管道中用$expr做字段匹配,配合$limit:1避免返回多余数据
  • 使用$size:0替代$eq:[],MongoDB对$size的判断逻辑更高效

方案3:创建覆盖复合索引

为conversations2创建覆盖索引,让查询完全通过索引完成,不需要回表读取文档数据,进一步提升速度。

创建索引:

db.conversations2.createIndex({ userId: 1, otherUserId: 1, unreadMessageCount: 1 })

该索引包含了$match阶段需要过滤的userId、otherUserId,以及$group阶段求和需要的unreadMessageCount,MongoDB可以直接从索引中获取所有需要的数据,无需访问文档本身。

验证优化效果

可以通过explain("executionStats")查看查询计划,确认索引是否被命中,以及查询的耗时、扫描行数等指标:

db.conversations2.aggregate([/* 优化后的查询 */]).explain("executionStats")

内容的提问来源于stack exchange,提问作者Mevo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 09:45:00