MongoDB - 关系
MongoDB - 关系数据建模
Section titled “MongoDB - 关系数据建模”在 MongoDB 中,关系表示不同文档之间是如何逻辑连接的。这些连接可以通过两种主要策略进行建模:嵌入式(非范式化)和引用式(范式化)。选择哪种策略取决于您的应用程序的数据访问模式,它可以建模一对一(1:1)、一对多(1:N)和多对多(N:N)关系。
让我们使用一个常见场景来探讨这些概念:一个用户拥有多个地址。这代表一种一对多(1:N)关系。
一个现代的用户文档可能看起来像这样:
// users 集合{ "_id": new ObjectId(), "name": "Alex Doe", "email": "alex.doe@example.com", "createdAt": new ISODate()}以及一个关联的地址文档:
// addresses 集合(用于引用模型){ "_id": new ObjectId(), "street": "123 Main St", "city": "Anytown", "state": "CA", "zip": "12345"}策略 1:使用嵌入式文档进行建模
Section titled “策略 1:使用嵌入式文档进行建模”在嵌入式方法中,您将相关数据直接放置在父文档内部。对于我们的用户和地址示例,我们将在用户文档中嵌入一个地址子文档数组。
// 带有嵌入式地址的用户db.users.insertOne({ "_id": new ObjectId(), "name": "Alex Doe", "email": "alex.doe@example.com", "createdAt": new ISODate(), "addresses": [ { "type": "Home", "street": "123 Main St", "city": "Anytown", "state": "CA", "zip": "12345" }, { "type": "Work", "street": "456 Business Ave", "city": "Metropolis", "state": "NY", "zip": "54321" } ]});这种方法将所有相关数据整合到单个文档中,为读取操作提供了显著的性能优势。整个用户档案,包括所有地址,可以通过单次查询获取:
// 一次性检索用户及其地址db.users.findOne({ "email": "alex.doe@example.com" });嵌入式的优缺点
Section titled “嵌入式的优缺点”优点:读取更快(无需数据库联接)、数据局部性,以及整个文档的原子更新。 缺点:可能导致大型文档(BSON 文档最大 16MB),如果嵌入信息在其他地方也需要,可能导致数据重复,如果嵌入数组变得非常大,写入速度可能会变慢。
策略 2:使用文档引用进行建模
Section titled “策略 2:使用文档引用进行建模”引用式(或范式化)方法将相关数据保存在单独的集合中。父文档存储对相关文档的引用——通常是 _id 值。
// 引用地址 ID 的用户文档db.users.insertOne({ "_id": new ObjectId(), "name": "Alex Doe", "email": "alex.doe@example.com", "createdAt": new ISODate(), "address_ids": [ new ObjectId("615cba23...1"), // 第一个地址文档的 ID new ObjectId("615cba23...2") // 第二个地址文档的 ID ]});
// 单独集合中的地址文档db.addresses.insertMany([ { "_id": new ObjectId("615cba23...1"), "street": "123 Main St", ... }, { "_id": new ObjectId("615cba23...2"), "street": "456 Business Ave", ... }]);要检索用户的地址,您将使用 $lookup 聚合阶段,它执行与其他集合的左外连接。这是处理引用的现代、高效方式。
// 使用 $lookup 连接用户和地址db.users.aggregate([ { $match: { "email": "alex.doe@example.com" } // 查找用户 }, { $lookup: { from: "addresses", // 要连接的集合 localField: "address_ids", // 输入文档(用户)中的字段 foreignField: "_id", // 'from' 集合文档中的字段 as: "address_info" // 要添加到输入文档的新数组字段 } }]).pretty();引用式的优缺点
Section titled “引用式的优缺点”优点:避免数据重复,使文档更小,并且当相关数据需要频繁更新或独立访问时,这种方式更好。它对于复杂的 N:N 关系更灵活。
缺点:需要额外的 $lookup 操作,这可能比读取单个嵌入式文档慢。检索完整的实体需要多次集合访问。
最佳实践:选择正确的模型
Section titled “最佳实践:选择正确的模型”选择并非总是二元的。混合方法通常是最佳的。对于包含性的、具有明确父子动态且主要一起访问的关系(例如,博客文章的评论),请使用嵌入式。当相关数据量大、独立访问或作为多对多关系的一部分时(例如,产品和类别),请使用引用式。