嵌入 vs 引用:两种建模方式
本教程共 50 篇 · 第 36 篇 · 更新于 2026-07-30 · 约 4 分钟阅读
36. 嵌入 vs 引用:两种建模方式
本节目标:搞懂 MongoDB 两种关联数据的方式,学会在「嵌入」和「引用」之间做正确取舍。
关系型数据库喜欢把数据拆成多张表,再用 JOIN 拼回来。MongoDB 走了另一条路:它允许你把数据塞进同一个文档(Document),也能像传统方式那样用 ID 去关联别的集合(Collection)。这两种做法,就是嵌入与引用。
36.1 嵌入:把数据放在一个文档里
嵌入(Embedding)就是在一个文档内部,用内嵌文档或数组把相关数据一起存下来。比如用户的收货地址,直接写进用户文档里。
// 把地址嵌入用户文档
test> db.users.updateOne(
{ _id: 1 },
{ $set: {
profile: { phone: "13800000000", bio: "爱折腾的后端" }
}}
)
这样取用户时,连地址一起拿到了,一次读取就够。我之前踩过的坑,就是过早把什么都拆表,结果一个页面要查五六次库。
Tip一句话口诀:经常一起读的数据,就放在一起。这能省掉大量跨集合查询。
36.2 引用:用 ID 跨集合关联
引用(Referencing)则是把数据放另一个集合,用 _id 互相指。比如订单存在 orders 集合,只记一个 user_id 指向 users。
// 订单引用用户,而不是把用户信息抄进来
test> db.orders.insertOne({
user_id: 1,
items: [{ product: "键盘", qty: 1, price: 199 }],
total: 199,
status: "paid",
created_at: new Date()
})
查的时候先拿订单,再用 user_id 去 users 找用户。这种写法避免了重复存储,但要多查一次。
Note引用本质就是「我在别处,凭 ID 来找我」。它和关系库的「外键」思路很像,只是 MongoDB 不强制约束。
36.3 怎么选:读多写少 vs 需独立访问
选嵌入还是引用,看数据的访问模式,而不是看「关系是一对多还是多对多」。
优先用嵌入,当数据满足这几点:
- 总是和用户一起被读取。
- 数据量小、不会无限增长。
- 写操作以「整块更新」为主。
优先用引用,当数据满足这几点:
- 需要被独立访问,比如订单要单独查、单独分页。
- 一方会 unbounded 增长,比如一个用户的上千条订单。
- 同一份数据被多处引用,复制一份会很难维护。
Warning数组不是无限大的。MongoDB 单个文档上限 16MB。把会无限增长的子数据嵌入,迟早撑爆文档。
36.4 原子性边界:单文档操作天然安全
MongoDB 保证「单个文档内的写操作是原子的」。也就是说,你在一个文档里改嵌入地址和改用户名,要么都成功,要么都失败。
// 单文档更新是原子的,嵌入数据一起变
test> db.users.updateOne(
{ _id: 1 },
{ $set: { name: "小明", "profile.phone": "13900000000" } }
)
但跨文档、跨集合就不天然原子了。比如「扣库存」和「建订单」分属两个集合,单条命令无法保证两者同时成功。
Note正因为单文档原子,很多场景下用嵌入就能躲开事务的复杂度。后面第 49 章会讲,跨文档才需要多文档事务。
36.5 小结:建模是取舍,不是对错
嵌入带来快读和原子性,代价是数据可能冗余。引用带来灵活和独立,代价是多一次查询、失去天然原子性。没有标准答案,只有适合你访问模式的答案。
下三章我们拆开看:一对一、一对多、多对多,分别该怎么建。