必须用独立 friendships 集合建模,嵌入式数组会导致文档超限、更新不一致、反向查询全表扫描三连崩;正确做法是建 friendships 集合并为 user_id 和 friend_id 分别建单字段索引,缺一不可。

必须用独立 friendships 集合建模,嵌入式数组(如 followers: ["u2", "u3"])在真实业务中会直接触发文档超限、更新不一致、反向查询全表扫描三连崩。
为什么不能把关注关系嵌进用户文档里
看似省一个集合,实则埋了三个硬伤:
-
16MB文档上限极易撞线:用户有5000+关注者时,仅 ID 数组就接近100KB;若再加时间戳、状态、备注等字段,撑爆是常态 - 加关注/取关需跨文档原子写:要同时
$push到u1的following和u2的followers,MongoDB 不支持跨文档事务(即使开启 multi-document transaction,高并发下性能断崖下跌) - 查“谁关注了
u2”无法建索引:db.users.find({ following: "u2" })是 collection scan,QPS 过百就抖动,P99延迟秒级起步
正确建模:friendships 集合 + 双向单字段索引
每条记录表示一次单向关注行为,结构极简:
db.friendships.insertOne({
user_id: "u1",
friend_id: "u2",
created_at: new Date(),
status: "active" // 可扩展: pending / blocked / muted
})
立刻执行以下两条索引命令,缺一不可:
-
db.friendships.createIndex({ user_id: 1 })—— 支持查 “u1关注了谁”,走索引 range scan -
db.friendships.createIndex({ friend_id: 1 })—— 支持查 “谁关注了u2”,否则find({ friend_id: "u2" })就是全表扫
漏掉任一索引,反向查询性能归零;复合索引(如 {user_id: 1, friend_id: 1})对反向查询无效,别被误导。
$graphLookup 查二度好友的写法与致命坑
它不是图数据库,$graphLookup 是聚合阶段模拟遍历,极易因参数错配 OOM 或超时:
-
maxDepth必须显式设值:二度好友(u1 → u2 → u3)对应maxDepth: 1,不是2;不设等于无限递归 -
connectFromField和connectToField必须严格匹配字段名:存的是user_id/friend_id,就不能写成from/to -
startWith是起点值,不是字段路径:写"$friend_id"(字符串值),不是"$friends"(数组会静默失败)
高频接口别塞权重计算:共同好友数、最近互动时间等逻辑放聚合里,P99 轻松破 300ms;应离线预计算并存到 recommend_scores 集合。
真正卡住性能的往往不是查询语句本身,而是索引缺失或 maxDepth 写错——这两个点线上出过太多次 OOM 和超时告警,务必在上线前用真实数据量压测一遍。











