MongoDB 基础系列五:数据建模之一对一、一对多、多对对建模关系

前言

此篇博文是 Mongdb 基础系列之一;主要介绍 MongoDB 的数据建模相关内容;

本文为作者的原创作品,转载需注明出处;

One-to-One Relationship with Embedded documents

正如建模章节 Embedded 所描述的那样,当两个实体之间存在 “Contains” 的关系,并且是一对一的关系的时候,使用 Embedded documents;下面以顾客( patron )和地址(address )为例来阐述一下这种关系,

如果使用的是 Reference 既 normolized 数据建模的方式,我们将会有如下的建模方式,

1
2
3
4
5
6
7
8
9
10
11
12
{
_id: "joe",
name: "Joe Bookreader"
}

{
patron_id: "joe",
street: "123 Fake Street",
city: "Faketon",
state: "MA",
zip: "12345"
}

在 address 实例中,通过外键 patron_id 引用 patron 实例对象;当要获取 patron 的 address 信息的时候,需要额外的查询,查找 patron 中的 patron_id 是否与 patron 匹配,然后返回相应的 addresses 信息;当有多个外键关联的时候,这样的查询是非常耗费性能的;

所以,通过使用 Embedded 的建模方式,可以有效的避免这种性能上的开销;

1
2
3
4
5
6
7
8
9
10
{
_id: "joe",
name: "Joe Bookreader",
address: {
street: "123 Fake Street",
city: "Faketon",
state: "MA",
zip: "12345"
}
}

One-to-Many Relationships with Embedded Documents

同样正如建模章节 Embedded 所描述的那样,当一个 patron 包含多个 addresses 的时候并且无疑,这些 addresses 只属于该 patron;假设,我们有如下的 normolized 建模方式,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
_id: "joe",
name: "Joe Bookreader"
}

{
patron_id: "joe",
street: "123 Fake Street",
city: "Faketon",
state: "MA",
zip: "12345"
}

{
patron_id: "joe",
street: "1 Some Other Street",
city: "Boston",
state: "MA",
zip: "12345"
}

通过标准的建模方式,严格使用外键的方式来进行建模;这样导致在查询的时候,有更多的查询性能上的开销;所以,同上一小节那样,我们可以对该一对多的方式使用 Embedded 既 unnormolized 的建模方式来进行优化,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
_id: "joe",
name: "Joe Bookreader",
addresses: [
{
street: "123 Fake Street",
city: "Faketon",
state: "MA",
zip: "12345"
},
{
street: "1 Some Other Street",
city: "Boston",
state: "MA",
zip: "12345"
}
]
}

One-to-Many Relationships with Document References

考虑 publisher 与 book 之间的关系,下面的这个例子将会展示使用 References 比 Embedded 更好,可以有效的减少数据的冗余;如果我们将 publisher 的数据嵌入到 book 将会导致数据严重的冗余,不利于将来的维护;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
title: "MongoDB: The Definitive Guide",
author: [ "Kristina Chodorow", "Mike Dirolf" ],
published_date: ISODate("2010-09-24"),
pages: 216,
language: "English",
publisher: {
name: "O'Reilly Media",
founded: 1980,
location: "CA"
}
}

{
title: "50 Tips and Tricks for MongoDB Developer",
author: "Kristina Chodorow",
published_date: ISODate("2011-05-06"),
pages: 68,
language: "English",
publisher: {
name: "O'Reilly Media",
founded: 1980,
location: "CA"
}
}

所以,为了消除这种冗余,我们可以将 Embedded 的建模方式改为 References 的方式,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
name: "O'Reilly Media",
founded: 1980,
location: "CA",
books: [123456789, 234567890, ...]
}

{
_id: 123456789,
title: "MongoDB: The Definitive Guide",
author: [ "Kristina Chodorow", "Mike Dirolf" ],
published_date: ISODate("2010-09-24"),
pages: 216,
language: "English"
}

{
_id: 234567890,
title: "50 Tips and Tricks for MongoDB Developer",
author: "Kristina Chodorow",
published_date: ISODate("2011-05-06"),
pages: 68,
language: "English"
}

但是上面的这种方式存在的问题是,如果 publisher document 中的 books 字段值是没有上限的,那么该字段值将会永无止境的增长,所以,考虑到将来的扩展性,可以将上面的 References data model 进行进一步的优化,让 book 对象通过外键的方式来关联 publisher,这个做法有个专有的名词,叫做 reverse control

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
_id: "oreilly",
name: "O'Reilly Media",
founded: 1980,
location: "CA"
}

{
_id: 123456789,
title: "MongoDB: The Definitive Guide",
author: [ "Kristina Chodorow", "Mike Dirolf" ],
published_date: ISODate("2010-09-24"),
pages: 216,
language: "English",
publisher_id: "oreilly"
}

{
_id: 234567890,
title: "50 Tips and Tricks for MongoDB Developer",
author: "Kristina Chodorow",
published_date: ISODate("2011-05-06"),
pages: 68,
language: "English",
publisher_id: "oreilly"
}

Reference

https://docs.mongodb.com/manual/tutorial/model-embedded-one-to-one-relationships-between-documents/