MongoDB 入门教程
多对多关系建模
本教程共 50 篇 · 第 39 篇 · 更新于 2026-07-30 · 约 3 分钟阅读
MongoDBMongoDB 入门教程多对多数据建模中间集合双向引用
39. 多对多关系建模
本节目标:理解多对多关系,会用中间集合建模,并看清双向引用的维护成本。
多对多(Many-to-Many)指两边都能对应多个对方。一个用户买过很多商品,一个商品被很多用户买。我们的 users、products、orders 三张表,正好构成这种关系。
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)建模,省心又不易出错。只有读极多、写极少且关系稳定时,才考虑双向引用,并务必保证双写一致。