MongoDB - 数据库引用
MongoDB - 数据关系建模
Section titled “MongoDB - 数据关系建模”与传统 SQL 数据库不同,MongoDB 的灵活模式提供了多种建模数据关系的方式。选择嵌入(embedding)或引用(referencing)数据是模式设计中最关键的决策之一,它会影响应用程序的性能、数据完整性和可伸缩性。
模式 1:嵌入文档(非范式化)
Section titled “模式 1:嵌入文档(非范式化)”嵌入式是指将相关数据存储在单个父文档中。此模式也称为非范式化(denormalization)。
示例:包含评论的产品
Section titled “示例:包含评论的产品”考虑一个产品有一些评论。由于评论与产品紧密耦合,我们可以将它们作为数组嵌入到产品文档中。
{ _id: ObjectId("..."), name: "Wireless Mouse", price: 25, // 评论直接嵌入在此处 reviews: [ { username: "alice", rating: 5, comment: "Excellent product!" }, { username: "bob", rating: 4, comment: "Good value for the price." } ]}何时使用嵌入式模式
Section titled “何时使用嵌入式模式”- 一对少关系: 当相关项的数量较少且有限时,此模式是理想选择。
- 数据检索: 当您需要同时检索主文档及其相关数据时,嵌入式提供最佳性能,因为它只需要一次数据库读取。
- 原子性: 对文档(包括其嵌入数据)的更新是原子性的。
- 最佳应用场景: 用户的联系信息、购物车中的商品、产品的评论。
模式 2:引用文档(范式化)
Section titled “模式 2:引用文档(范式化)”引用,或范式化(normalization),是指将相关数据存储在单独的集合中。您通过在一个文档中存储另一个文档的 _id 来创建它们之间的链接。这是大多数“一对多”关系推荐的方法。
示例:博客文章和作者
Section titled “示例:博客文章和作者”一位作者可以撰写多篇文章。如果在每篇文章中都存储作者的完整详细信息将是冗余的。相反,我们在文章文档中存储作者的 _id。
// `authors` 集合{ _id: ObjectId("author1"), name: "Chris", email: "chris@example.com"}
// `posts` 集合{ _id: ObjectId("post123"), title: "Schema Design Best Practices", content: "...", // 手动引用作者 author_id: ObjectId("author1")}何时使用引用模式
Section titled “何时使用引用模式”- 一对多关系: 当“多”方数据量很大或无上限时(例如,事件的日志,热门文章的评论)。
- 避免数据重复: 当引用数据量大且频繁更新时(例如,用户资料)。
- 多对多关系: 例如,一个
students集合和一个courses集合,通过存储两者引用的第三个集合进行链接。 - 超出文档大小限制: 当嵌入会导致文档超出 16MB 限制时。
解析引用:使用 $lookup 的现代方法
Section titled “解析引用:使用 $lookup 的现代方法”引用的主要缺点是它需要单独的查询来获取相关数据。解决此问题的现代高效方法是使用 $lookup 聚合阶段,它执行服务器端“连接(join)”操作。
// 此聚合管道查找一篇文章并“连接”其作者信息db.posts.aggregate([ { $match: { _id: ObjectId("post123") } // 查找特定文章 }, { $lookup: { from: "authors", // 要连接的集合 localField: "author_id", // 输入文档(posts)中的字段 foreignField: "_id", // 'from' 集合(authors)文档中的字段 as: "authorDetails" // 要添加到输入文档的新数组字段 } }])
// 结果包含文章,其作者详细信息嵌入在数组中:/*{ _id: ObjectId("post123"), title: "Schema Design Best Practices", author_id: ObjectId("author1"), authorDetails: [ { _id: ObjectId("author1"), name: "Chris", email: "chris@example.com" } ]}*/遗留模式:DBRefs
Section titled “遗留模式:DBRefs”DBRefs 是一种遗留的、正式的文档引用约定。一个 DBRef 是一个包含三个字段的文档:$ref(集合名称)、$id(被引用文档的 _id)和一个可选的 $db。
// 使用 DBRef 的文档示例{ _id: ObjectId("user1"), name: "Tom Benzamin", // 指向 'addresses' 集合中某个文档的 DBRef address: { "$ref": "addresses", "$id": ObjectId("addr1"), "$db": "w3" }}为什么不再推荐使用 DBRefs
Section titled “为什么不再推荐使用 DBRefs”尽管它们存在,但 DBRefs 不再是推荐的关系建模方式。现代最佳实践强烈倾向于使用 $lookup 管道进行手动引用,因为:
- 缺乏自动解析: 大多数 MongoDB 驱动程序不会自动解析 DBRefs。您仍然需要编写应用程序级代码来执行第二次查询,就像处理手动引用一样。
- 增加复杂性: 与简单地存储
ObjectId相比,它们会为您的文档增加不必要的冗余和复杂性。 $lookup更优:$lookup聚合阶段功能更强大、更灵活。它与简单的手动引用无缝协作,并能对连接的数据提供更多控制。
总结与最佳实践
Section titled “总结与最佳实践”从嵌入式模式开始。 如果您的数据关系是一对少,并且数据通常一起访问,嵌入式模式会更快。如果嵌入式模式导致数据重复、文档过大,或者您有一对多关系,请切换到手动引用。在需要时,使用 $lookup 聚合管道高效检索相关数据。除非您有特定、充分的遗留原因或需要从单个字段引用多个集合的特殊用例,否则请避免使用 DBRefs。