在MongoDB这样的文档数据库中,可以根据任何特定应用的需求灵活定义数据模式,这是其一个显著特征。在文档内部嵌套文档是创建最佳模式的关键技术。您不必构建与应用程序严格匹配的数据模型,而可以构建与您的用例和应用功能匹配的数据模型。
在关系型数据库中,每个实体都存储在自己的表中,并通过外键相互关联。虽然MongoDB当然支持从一个文档到另一个文档的引用,甚至多文档连接,但像使用关系型数据库那样使用文档数据库是一个错误。
例如,让我们看看一个简单的结构,包含一个用户及其地址。这两种实体之间关系的结构化方式之一是使用引用
> db.user.findOne()
{
_id: 111111,
email: “[email protected]”,
name: {given: “Jane”, family: “Han”},
}
> db.address.find({user_id: 111111})
{
_id: 121212,
street: “111 Elm Street”,
city: “Springfield”,
state: “Ohio”,
country: “US”,
zip: “00000”
}
但是,如果这个地址仅与这个用户相关联,或者需要频繁地与用户一起访问,直接将地址文档嵌入到用户文档中会更简单,如下所示
> db.user.findOne({_id: 111111})
{
_id: 111111,
email: “[email protected]”,
name: {given: “Jane”, family: “Han”},
address: {
street: “111 Elm Street”,
city: “Springfield”,
state: “Ohio”,
country: “US”,
zip: “00000”,
}
}
现在,您不必针对地址集合执行单独的查询来检索Jane Han的地址,只需将其作为她的用户记录的子文档访问即可。
存储多个地址同样简单
> db.user.findOne({_id: 111111})
{
_id: 111111,
email: “[email protected]”,
name: {given: “Jane”, family: “Han”},
addresses: [
{
label: “Home”,
street: “111 Elm Street”,
city: “Springfield”,
state: “Ohio”,
country: “US”,
zip: “00000”,
},
{label: “Work”, ...}
]
}
您甚至可以使用位置操作符更新单个地址
> db.user.update(
{_id: 111111,
“addresses.label”: “Home”},
{$set: {“addresses.$.street”: “112 Elm Street”}}
)
请注意,您需要将包含点(如“address.label”)的任何查询或更新键用引号括起来,以便在语法上正确。更新部分的查询需要包含您要更新的数组字段,然后更新将应用于与数组中匹配的第一个元素。
嵌套文档是存储相关数据(特别是经常一起访问的数据)的效率高且清晰的方法。通常,在设计MongoDB的数据模式时,您应默认优先选择嵌入,仅在值得时使用引用和应用程序或数据库端的连接。
嵌套的模式有几种不同的模式
这是优先在文档中嵌入复杂子结构的一般模式。典型规则是
使用在一起,存储在一起。一种混合情况,当您有一个可能非常长的相关项目列表的独立集合时,但您又想方便地将其中一些项目显示给用户。让我们看一个例子
> db.movie.findOne()
{
_id: 333333,
title: “The Big Lebowski”
}
> db.review.find({movie_id: 333333})
{
_id: 454545,
movie_id: 333333,
stars: 3
text: “it was OK”
}
{
_id: 565656,
movie_id: 333333,
stars:5,
text: “the best”
}
...
现在假设您有成千上万的评论,但每次显示电影时,您总是显示最新的两个。在这种情况下,将这个子集作为列表存储在电影文档中是有意义的。
> db.movie.findOne({_id: 333333})
{
_id: 333333,
title: “The Big Lebowski”,
recent_reviews: [
{_id: 454545, stars: 3, text: “it was OK”},
{_id: 565656, stars: 5, text: “the best”}
]
}
如果您经常访问相关项目的子集,请嵌入该子集。另一种混合情况称为扩展引用。它类似于子集模式,因为它优化了存储在需要的地方的一小部分经常访问的信息。在这种情况下,它不是列表,而是当一份文档引用另一份在其自己的集合中,同时也存储了该其他文档的一些字段以方便访问时使用。
示例
> db.movie.findOne({_id: 444444})
{
_id: 444444,
title: “One Flew Over the Cuckoo's Nest”,
studio_id: 999999,
studio_name: “Fantasy Films”
}
正如您所看到的,存储了studio_id,以便您可以查找有关制作电影的公司的更多信息,但公司的名称也复制到这个文档中以便于显示。请注意,如果您从经常更改的文档中嵌入信息,您需要记住在信息更改时更新您已复制该信息的文档。
如果您经常访问引用文档的几个字段,请嵌入这些字段。在所属文档中存储短的相关信息列表是非常有意义的,但如果您的列表可以无限制地增长,将其放在单个文档中不仅不明智,而且无法维持!MongoDB对单个文档的大小有限制,这是其中之一 - 但此外,如果文档经常被访问,您将开始看到过度使用内存的负面影响。
如果列表可以无限制地增长,请将其放入自己的集合。您应该在单独的集合中存储子文档的另一个情况是,当您想独立于您原本打算嵌套的父文档访问它们时。例如,考虑一家公司制造的产品。如果公司只销售少量产品,您可能希望将它们作为公司文档的一部分存储。然而,如果您想直接通过SKU访问那些产品,或者跨公司重用它们,您可能还想将它们存储在自己的集合中。
如果您经常独立访问或操作实体,请将其放入自己的集合。在自定义应用程序中嵌入MongoDB图表。