您当前的嵌入式架构设计有其优点,其中之一是数据局部性。由于 MongoDB 将数据连续存储在磁盘上,因此将所需的所有数据放在一个文档中可确保旋转磁盘花费更少的时间来寻找磁盘上的特定位置。
如果您的应用程序经常访问books 信息以及Authors 数据,那么您几乎肯定会希望采用嵌入式路由。嵌入文档的另一个优点是写入数据的原子性和隔离性。
为了说明这一点,假设您希望一位作者的所有书籍都更新他的电子邮件字段,这可以通过一个(原子)操作来完成,这不是 MongoDB 的性能问题:
db.books.updateMany(
{ "Authors.name": "foo" },
{
"$set": { "Authors.$.email": "new@email.com" }
}
);
或使用早期的 MongoDB 版本:
db.books.update(
{ "Authors.name": "foo" },
{
"$set": { "Authors.$.email": "new@email.com" }
},
{ "multi": true }
)
在上面,您使用positional $ operator,它通过识别数组中要更新的元素而不显式指定数组中元素的位置来促进对包含嵌入文档的数组的更新。将它与$ 运算符上的dot notation 一起使用。
有关 MongoDB 中数据建模的更多详细信息,请阅读文档Data Modeling Introduction,尤其是Model One-to-Many Relationships with Embedded Documents。
您可以考虑的另一个设计选项是引用您遵循规范化架构的文档。例如:
// db.books schema
{
"_id": 3
"authors": [1, 2, 3] // <-- array of references to the author collection
"title": "foo"
}
// db.authors schema
/*
1
*/
{
"_id": 1,
"name": "foo",
"surname": "bar",
"address": "xxx",
"email": "foo@mail.com"
}
/*
2
*/
{
"_id": 2,
"name": "abc",
"surname": "def",
"address": "xyz",
"email": "abc@mail.com"
}
/*
3
*/
{
"_id": 3,
"name": "alice",
"surname": "bob",
"address": "xyz",
"email": "alice@mail.com"
}
当您具有非常不可预测的数量的一对多关系时,上述使用文档引用方法的规范化模式也具有优势。如果每个图书实体有数百或数千个作者文档,则嵌入在空间限制方面会遇到很多挫折,因为文档越大,它使用的 RAM 越多,而 MongoDB 文档的硬大小限制为 16MB。
对于查询规范化架构,您可以考虑使用聚合框架的 $lookup 运算符,该运算符对同一数据库中的 authors 集合执行左外连接,以过滤来自 @ 的文档987654337@收集处理。
因此,我相信您当前的架构比创建 authors 的单独集合更好,因为单独的集合需要更多的工作,即查找一本书 + 它的作者是两个查询并且需要额外的工作,而上述架构嵌入文档简单快捷(单次搜索)。插入和更新没有太大区别。因此,如果您需要选择单个文档、需要对查询进行更多控制或拥有大量文档,则单独的集合是很好的选择。当您想要整个文档时,嵌入文档也很好,该文档具有 $slice 的嵌入 authors,或者根本没有 authors。
一般的经验法则是,如果您的应用程序的查询模式广为人知,并且往往只能以一种方式访问数据,那么嵌入式方法就可以很好地工作。如果您的应用程序以多种方式查询数据,或者您无法预测数据查询模式,则更规范化的文档引用模型将适合这种情况。
参考:
MongoDB Applied Design Patterns: Practical Use Cases with the Leading NoSQL Database By Rick Copeland