MongoDB 快速入门实战教程进阶篇 一:数据模型
前一部分的文章:
MongoDB 快速入门实战教程基础篇 一 :文档的 CR操作
MongoDB 快速入门实战教程基础篇 一 :文档的 UD操作
MongoDB 快速入门实战教程基础篇 二: 流式聚合操作
MongoDB 快速入门实战教程基础篇 三:执行计划与索引
进阶篇 一 数据模型
MongoDB 不强制执行文档结构,也就是说我们不需要在数据插入前定义文档的结构,也不要求集合中的文档具有相同的结构。要知道,在 MySQL 这样的关系型数据库中,我们必须在插入数据前定义数据模型(即创建表和定义字段),否则无法向将数据写入到数据库中。MongoDB 的文档结构非常灵活,具体表现在以下方面:
- 集合中的文档可以使用不同的字段;
- 文档的字段类型可以不同;
- 假如要更改文档的结构(例如删除字段、添加字段或更新字段值的类型),只需要执行更新操作即可。
文档结构
为 MongoDB 应用程序设计数据模型的关键决策围绕文档结构以及应用程序如何表示数据之间的关系。MongoDB允许将相关数据嵌入到单个文档中。MongoDB 中有两种文档结构:嵌入式和规范化。
嵌入式结构
MongoDB 允许将文档嵌入到字段或数组中,包含嵌入文档的文档结构叫做嵌入式结构。嵌入式结构通过在单个文档中存储相关数据来描述数据之间的关系或将数据关联在一起。嵌入式结构的文档如下:
{
_id: 1,
username: "123xyz",
contact: {
phone: "123-456-789",
email: "xyz@example.com"
},
access: {
level: 5,
group: "dev"
}
}
由于 contact
字段 和 access
字段的值是文档,所以这两个字段对应的值被称为嵌入式文档(又称内嵌文档),_id
为 1
的文档则是它们的外层文档。我们可以使用点符号访问嵌入式文档中的字段,例如使用 contact.phone
这种方式访问 contact
中的 phone
字段。
规范化结构
规范化结构通过引用的方式来描述数据之间的关系或将数据关联在一起。这与 SQL 数据库中的 Foreign key
概念相似。规范化结构关联文档的方式如下图所示:
上图示例中,集合 contact
与集合 access
中的 user_id
字段均引用自集合 user
中的 _id
字段。规范化结构的存在使得我们可以描述更复杂的多对多关系。
文档结构与模型关系
嵌入式结构和规范化结构的适用场景不同,我们在设计数据模型时应该考虑哪些因素呢?下面将通过几个示例来了解这些场景。
嵌入式结构很适合有包含关系的情况,以下示例用规范化结构来表示客户 James
与其收货地址的关系:
{
_id: "1",
user_id: 30,
name: "James"
}
{
user_id: "30",
address: "虹桥机场员工宿舍C区5楼",
city: "上海",
phone: "13005920000",
zip: "200020"
}
记录收货地址信息的文档引用了用户信息文档中的用户 user_id
字段。如果经常检索收货地址信息,那么就会发出多个查询。更好的数据模型是将用户信息和收货地址信息放在同一个文档,例如:
{
_id: "1",
user_id: 30,
name: "James",
adres:
{
address: "虹桥机场员工宿舍C区5楼",
city: "上海",
phone: "13005920000",
zip: "200020"
}
}
使用嵌入式结构后,只需要一次查询就可以得到完整的结果。用户通常会有多个收货地址,即数据模型要满足一对多的关系。以下示例用规范化结构来表示客户 James
与其多个收货地址的关系:
{
_id: <ObjectId1>,
user_id: 30,
name: "James"
}
{
_id: <ObjectId2>
user_id: <ObjectId1>,
address: "虹桥机场员工宿舍C区5楼",
city: "上海",
phone: "13005920000",
zip: "200020"
}
{
_id: <ObjectId3>
user_id: <ObjectId1>,
address: "白云机场员工宿舍A区6楼",
city: "广州",
phone: "13005920000",
zip: "510080"
}
也可以使用嵌入式结构来表示一对多关系,示例如下:
{
_id: "1",
user_id: 30,
name: "James",
adres:[
{
address: "虹桥机场员工宿舍C区5楼",
city: "上海",
phone: "13005920000",
zip: "200020"
},
{
address: "白云机场员工宿舍A区6楼",
city: "广州",
phone: "13005920000",
zip: "510080"
}]
}
当然,用规范化结构来描述一对多关系也有好处。通常情况下,论坛用户会频繁地发布自己的观点(发帖或回帖),如果用嵌入式结构存储观点发布者和观点内容,那么就会造成发布者信息重复。对应示例如下:
{
title: "吉利星越大型试驾活动,发夹弯见!",
publisher_id: 29019,
publisher: "车评人陈杨",
pdate: ISODate("2019-01-01 12:03:36"),
view: 32810,
tag: ["吉利", "星越", "试驾"]
}
{
title: "宝马排名飙升 评2019上半年豪华品牌销量",
publisher_id: 29019,
publisher: "车评人陈杨",
pdate: ISODate("2019-01-01 12:03:50"),
view: 6020,
tag: ["宝马", "豪华", "奔驰"]
}
{
title: "试驾全新一代广汽传祺GA6",
publisher_id: 29019,
publisher: "车评人陈杨",
pdate: ISODate("2019-01-01 12:03:58"),
view: 10751,
tag: ["试驾", "广汽", "传祺"]
}
用规范化结构存储论坛用户观点,就可以节省非常多的空间,对应示例如下:
{
_id: <ObjectId1>
username: "chenyang",
nickname: "车评人陈杨"
}
{
_id: <ObjectId2>
title: "吉利星越大型试驾活动,发夹弯见!",
user_id: <ObjectId1>,
pdate: ISODate("2019-01-01 12:03:36"),
view: 32810,
tag: ["吉利", "星越", "试驾"]
}
{
_id: <ObjectId3>
title: "宝马排名飙升 评2019上半年豪华品牌销量",
user_id: <ObjectId1>,
pdate: ISODate("2019-01-01 12:03:50"),
view: 6020,
tag: ["宝马", "豪华", "奔驰"]
}
{
_id: <ObjectId4>
title: "试驾全新一代广汽传祺GA6",
user_id: <ObjectId1>,
pdate: ISODate("2019-01-01 12:03:58"),
view: 10751,
tag: ["试驾", "广汽", "传祺"]
}
嵌入式结构的读取操作性能比规范化结构更高,但嵌入式结构会导致数据重复,而规范化结构则不会。除了一对多关系之外,规范化结构还能够描述更复杂的多对多关系。
树状数据模型
嵌入式结构和规范化结构并不能满足所有的场景,例如树状关系的对象。 在这里插入图片描述如上图所示,树状关系的对象或实体是编程中常见的结构,下面我们就来学习树状结构的数据模型。
父引用树状数据模型
父引用树状数据模型通过在子节点中存储对父节点的引用来组织树状结构中的文档,对应示例如下:
> db.trees1.insertMany([
{ _id: "MongoDB", parent: "Databases" },
{ _id: "dbm", parent: "Databases" },
{ _id: "Databases", parent: "Programming" },
{ _id: "Languages", parent: "Programming" },
{ _id: "Programming", parent: "Books" },
{ _id: "Books", parent: null }
])
在这样的树状结构中,我们可以直接检索某个节点的父节点,例如检索 MongoDB
节点的父节点的示例如下:
> db.trees1.findOne({_id: "MongoDB"}).parent
Databases
也可以直接检索某个节点的子节点,例如检索 Databases
节点的子节点的示例如下: