应使用独立集合(如video_likes)存储用户互动关系,配合{videoId,userId}唯一索引与Redis计数缓存,避免将大量用户ID存入视频文档导致性能瓶颈。

直接存布尔字段或数组会撑爆文档、拖慢查询,尤其当单条视频有几十万用户互动时。MongoDB 本身不擅长高频更新超长数组,也别硬套关系型思路建关联集合——得用分层设计 + 合理索引。
点赞/收藏状态不能全塞进 video 文档里
常见错误是把 likedBy 或 collectedBy 设为用户 ID 数组,比如:{"_id": "v123", "title": "...", "likedBy": ["u1", "u2", ..., "u99999"]}。这会导致:
- 单文档逼近 16MB 上限(BSON 限制),插入/更新变慢甚至失败
- 每次点赞都要
$push或$pull,高并发下写锁争抢严重 - 查“谁点了赞”要全量加载数组,网络和内存开销大
- 无法对“某用户所有点赞”做高效聚合(比如翻页查他点过哪些视频)
推荐用独立集合 + 复合索引存状态
建两个轻量集合:video_likes 和 video_collections,每条记录只存一次互动关系:
{
"_id": ObjectId("..."),
"videoId": "v123",
"userId": "u456",
"createdAt": ISODate("2026-10-20T08:12:33Z")
}
关键操作建议:
- 写入前先用
findOne({videoId: "v123", userId: "u456"})判断是否已存在,避免重复插入 - 给
{videoId: 1, userId: 1}建唯一索引,防止脏数据,也加速去重判断 - 查“视频 v123 有多少人点赞” →
video_likes.countDocuments({videoId: "v123"}),比数组$size更准更快 - 查“用户 u456 点过哪些视频” →
video_likes.find({userId: "u456"}).sort({createdAt: -1}),配合{userId: 1, createdAt: -1}索引可分页
高频读场景加 Redis 缓存计数
如果首页、列表页频繁展示“点赞数”,别每次都查 MongoDB。用 Redis 的 INCR/DECR 维护实时计数:
- 点赞成功后:执行
INCR video:like:count:v123 - 取消点赞后:执行
DECR video:like:count:v123 - 展示时直接
GET video:like:count:v123,毫秒级响应 - 注意:Redis 计数需与 MongoDB 状态最终一致,可用定时任务或变更流(
change stream)做对账
别忽略用户态的原子性问题
点赞/取消是典型的“读-改-写”流程,MongoDB 默认不保证跨文档原子性。比如用户快速连点两次,可能造成:
- 第一次请求查到未点赞 → 插入记录 → 更新 Redis 计数
- 第二次请求在第一次写入完成前也查到未点赞 → 再次插入 → 重复记录(除非你用了唯一索引兜底)
所以必须依赖 {videoId, userId} 唯一索引拦截重复,而不是靠应用层逻辑判断。另外,Redis 的 INCR 是原子的,但要确保它和 MongoDB 写入在同一个事务边界内失败回滚——实际中建议用“先写 MongoDB,再更新 Redis”的顺序,并容忍极短时间内的计数偏差。











