首页 / MongoDB 入门教程 / 多对多关系建模

MongoDB 入门教程

多对多关系建模

本教程共 50 篇 · 第 39 篇 · 更新于 2026-07-30 · 约 3 分钟阅读

MongoDBMongoDB 入门教程多对多数据建模中间集合双向引用

39. 多对多关系建模

本节目标:理解多对多关系,会用中间集合建模,并看清双向引用的维护成本。

多对多(Many-to-Many)指两边都能对应多个对方。一个用户买过很多商品,一个商品被很多用户买。我们的 usersproductsorders 三张表,正好构成这种关系。

39.1 用中间集合当关联表

最干净的做法,是让 orders 充当中间集合(Join Collection)。它一头指用户,一头指商品,把多对多拆成两个一对多。

// 一笔订单,连接一个用户和多个商品
test> db.orders.insertOne({
  user_id: 1,
  items: [
    { product: "机械键盘", qty: 1, price: 399 },
    { product: "无线鼠标", qty: 2, price: 89 }
  ],
  total: 577,
  status: "paid",
  created_at: new Date()
})

想查「用户 1 买过哪些商品」,走订单即可:

test> db.orders.aggregate([
  { $match: { user_id: 1 } },
  { $unwind: "$items" },
  { $group: { _id: "$items.product", totalQty: { $sum: "$items.qty" } } }
])

想查「谁买过机械键盘」,同样查 orders 就行。中间集合让双向查询都不必改用户或商品。

Tip

中间集合是 MongoDB 处理多对多的首选。它把复杂关系变回我们熟悉的一对多,查询还灵活。

39.2 双向引用:方便但贵

另一种写法是两边各存一个引用数组。比如用户文档里记 bought:[商品id],商品文档里记 buyers:[用户id]

// 双向引用:两边各存对方 id 列表
test> db.users.updateOne({ _id: 1 }, { $addToSet: { bought: "kb-001" } })
test> db.products.updateOne({ _id: "kb-001" }, { $addToSet: { buyers: 1 } })

读的时候很爽,一次就拿全。代价在写:每发生一笔购买,要同时更新两个文档。

Warning

双向引用要求每次写都保持两边一致。任何一次更新失败,数据就对不上了。高并发下这很危险。

39.3 两种方式的代价对比

维度中间集合双向引用
读取需查中间集合(可聚合)一次读父文档即得
写入只写一笔订单须同步更新两个文档
一致性天然单点,不易漂移需应用层维护两边
适合关系频繁变动、需审计关系极稳定、读远多于写
Note

我之前在某项目用双向引用存「关注关系」,结果取消关注时漏改了一边,列表永远对不上。后来全改成中间集合,踏实多了。

39.4 小结

多对多优先用中间集合(如 orders)建模,省心又不易出错。只有读极多、写极少且关系稳定时,才考虑双向引用,并务必保证双写一致。