减少$lookup
操作
Overview
$lookup
操作根据指定字段连接同一数据库中两个集合的数据。 当您的数据结构类似于关系数据库并且需要对大型分层数据集进行建模时, $lookup
操作会非常有用。 但是,这些操作可能很慢并且会耗费大量资源,因为它们需要读取两个集合(而不是单个集合)并对其执行逻辑。
如果您经常运行$lookup
操作,请考虑重组您的模式,以便您的应用程序可以查询单个集合以获得所需的所有信息。 您可以利用 MongoDB灵活的模式模型以及嵌入式文档和数组来捕获单个文档结构中的数据之间的关系。 使用这种非规范化模型,可以充分利用 MongoDB 丰富的文档,并允许您的应用程序在单个查询中检索和操作相关数据。
示例
以下示例展示了两种旨在减少$lookup
操作的模式结构:
使用嵌入式文档
请考虑以下示例,一家杂货店跟踪两个单独集合中一对一的库存和营养信息。每个库存商品对应唯一的营养成分商品。nutrition_id
字段将 inventory
集合链接到 nutrition_facts
集合,类似于表格数据库:
// inventory collection { "name": "Pear", "stock": 20, "nutrition_id": 123, // reference to a nutrition_fact document ... } { "name": "Candy Bar", "stock": 26, "nutrition_id": 456, ... }
// nutrition_facts collection { "_id": 123, "calories": 100, "grams_sugar": 17, "grams_protein": 1, ... } { "_id": 456, "calories": 250, "grams_sugar": 27, "grams_protein": 4, ... }
如果应用程序通过name
请求库存列项的营养成分,则此模式结构需要nutrition_facts
集合的 { $lookup
} 来查找与库存列项的nutrition_id
匹配的条目。
相反,您可以将营养信息嵌入 inventory
集合中:
// inventory collection { "name": "Pear", "stock": 20, "nutrition_facts": { "calories": 100, "grams_sugar": 17, "grams_protein": 1, ... } ... } { "name": "Candy Bar", "stock": 26, "nutrition_facts": { "calories": 250, "grams_sugar": 27, "grams_protein": 4, ... } ... }
这样,当您查询 inventory
中的商品时,结果中就会包含营养成分,而无需再次查询或执行 $lookup
操作。当跨集合数据具有一对一的关系时,可考虑嵌入文档。
使用数组
请考虑以下示例,棒球联盟的 players
集合中的文档引用了 teams
集合中的文档,类似于表格数据库:
// players collection { "team_id": 1, // reference to a team document "name": "Nick", "position": "Pitcher" ... } { "team_id": 1, "name": "Anuj", "position": "Shortstop" ... }
// teams collection { "_id": 1, "name": "Danbury Dolphins" ... }
如果应用程序请求球队中的球员列表,则此模式结构需要players
集合的$lookup
} 来查找与team_id
匹配的每个球员。
相反,您可以列出球队文档本身的数组中的 players
:
// teams collection { "_id": 1, "name": "Danbury Dolphins", "players": [ { "name": "Nick", "position": "Pitcher" ... }, { "name": "Anuj", "position": "Shortstop" ... } ] }
通过利用数组来保存相关数据,应用程序可以检索完整的 team
信息,包括球队球员,而无需对其他集合执行 $lookup
操作或创建索引。在这种情况下,使用数组比将信息存储在单独的集合中有着更好的性能。
注意
以上示例中,棒球队球员人数是固定的, 不存在数组变得过大的风险。
数组注意事项
读取和写入大型数组的性能成本可能超过避免$lookup
操作所获得的好处。 如果您的数组无限制或非常大,这些数组可能会降低读写性能。
如果创建数组索引,数组中的每个元素都会被索引。 如果经常写入该数组, 索引或重新索引一个潜在的大数组字段的性能成本可能会很高。
非规范化
非规范化模式是指复制字段或从现有字段派生新字段的过程。 非规范化 可以在多种情况下提高读取性能,例如:
重复查询需要另一个集合中大型文档的几个字段。您可以选择在重复查询所针对的集合的嵌入式文档中维护这些字段的副本,以避免合并两个不同的集合或频繁执行
$lookup
操作。经常请求集合中某个字段的平均值。 您可以选择在单独的集合中创建派生字段, 在写入过程中予以更新,并保持该字段的运行平均值。
虽然对相关数据分组时首选嵌入没有重复的文档或数组, 但在必须维护单独的集合时, 非规范化可以提高读取性能。
注意
非规范化模式时,维护一致的重复数据就成了您的责任。
了解详情
模式的最佳结构取决于应用程序环境。 以下资源提供了有关数据建模的详细信息,以及嵌入式文档和数组的其他示例用例:
数据模型
要了解有关 MongoDB 数据建模和灵活的模式模型的更多信息,请参阅数据建模简介。
要详细了解嵌入式数据模型和规范化数据模型之间的权衡,请参阅数据模型设计。
MongoDB 还提供免费的 MongoDB University 数据建模课程:MongoDB 数据建模。
MongoDB.live 2020 数据建模演示
要了解如何将灵活数据模型整合到您的架构中,请参阅 MongoDB.live 2020 中的以下演示文稿:
通过 MongoDB 数据建模了解 MongoDB 中的实体关系及其实现示例。
通过高级模式设计模式了解您可以整合到模式的高级数据建模设计模式。
数组
要了解有关如何在 MongoDB 中查询数组的更多信息,请参阅查询数组。
设计模式(Design Patterns)
要了解数组运行良好的情况,请参阅以下设计模式:
非规范化模式
如需了解复制数据可优化模式的情况, 请参阅以下设计模式:
使用扩展参考模式将经常读取的数据部分从大型文档复制到较小的文档。