共计 3667 个字符,预计需要花费 10 分钟才能阅读完成。
MongoDB 存储引擎和数据模型设计
- 1. 存储引擎
- 1.1 存储引擎是什么
- 1.2 MongoDB 中的默认存储引擎
- 2. 数据模型设计
- 2.1 内嵌和引用
- 2.2 设计原则
- A. 1 – 1 或者 1 – *(较少)
- B. 1 – *(较多)
- C. 1 – *(非常多)
- D. * – *
- E. 通用建议
1. 存储引擎
1.1 存储引擎是什么
存储引擎是位于持久化数据(通常是放在磁盘或者内存中)和数据库之间的一个操作接口,它负责数据的存储和读取方式。MongoDB 数据库通过存储引擎在磁盘中读取数据,而假设我们的应用是 ASP.NET MVC,我们可以使用官方的 Mongo.Driver 驱动,通过通信协议(如 TCP)向 MongoDB 数据库发送各种请求。以下是一个简单的运行图示
1.2 MongoDB 中的默认存储引擎
自 MongoDB 3.2 Release 版本起,MongoDB 默认的存储引擎就成了 WiredTiger。而在之前的版本中,它还是 MMAPv1。但由于,ongoDB 架构支持可插拔的存储引擎,所以使用中即便要更换也是可以做到的。至于其他的功能比较大家可以参阅官方文档,如不再是 In-Place Update,新增 Compression 等。
我们可以在开启 mongod 服务时输入相关参数调整存储引擎,如 mongod –storageEngine MMAPv1|wiredTiger
我们也可以使用 db.collections.stats()查看当前的引擎名称
-
MMAPv1
MMAPv1 提供集合级别锁(实际上称为 collection-level locking) -
WiredTiger
WiredTiger 对于写操作提供文档级别并发控制(实际上称为 document-level concurrency),因此,不同的客户端请求可以在同一时间针对一个集合中的不同文档记性修改
2. 数据模型设计
2.1 内嵌和引用
在 MongoDB 中,数据的表示方式有内嵌和引用两种。
“引用”我们比较好理解,是指将不同实体的数据分散不到不同的集合中,而在关系型数据库设计中就是将实体分别建立相应的模型表。如常见的“老师 - 学生”,“产品 - 标签”关系,只要实体间存在关系,就可以使用“引用”思想。
“内嵌”是一种反范式化的设计,指的是将每个文档所需的数据都嵌入到文档内部,我想举一个“用户 - 账户”的关系。我们知道在领域驱动设计中,“用户”是一个聚合根,每个用户对应一个账户,所以是“1 对 1”的一种关系,在关系型数据库设计中,大部分时候都会将这两者严格区分开来。但是在 MongoDB 中,却不然,我们可以直接选择将“用户”需要的“账户”数据内嵌到用户文档中,便于我们的增删改查。这是一种反范式化的设计。
设计 MongoDB 数据模型的时候,我们需要转变以往设计关系型数据模型时的思维。即便是针对一个关系中不同集合的数量规模,我们的模型也将有很大的不同。
2.2 设计原则
**
A. 1 – 1 或者 1 – *(较少)
**
用户与账户,以及用户与收货地址都是这样情况,在这样的情况下,显而易见我们可以采取内嵌的方式来进行数据管理。
> db.person.findOne()
{_id:ObjectId("cccc"),
name:"wddpct",
age:22,
location:"wenzhou",
addresses:[{country:"china",city:"wenzhou",street:"chashan road"}
{country:"china",city:"wenzhou",street:"north center road"}
]
}
这也引伸出一个问题,除了“1”以外的另一端的实体是否有必要在数目较少的时候进行单独集合的储存。如用户和任务模块,任务是系统定期发布,分配给相应用户完成,这意味着我们对任务的操作也将比较复杂。这样的情况下,显然是分开不同集合进行存储,然后让 person 集合引用 task_id 数组。
> db.person.findOne()
{_id:ObjectId("cccc"),
name:"wddpct",
age:21,
location:"wenzhou",
tasks:[ObjectId("xxxx"),
ObjectId("yyyy"),
……
]
}
所以针对刚才提到的情况,我们大可以借鉴领域驱动模式中的“实体”和“值对象”的部分概念,主要还是看这些数据模型在系统中是否有较大较复杂的操作可能。
**
B. 1 – *(较多)
**
博主之前负责过一个市级地区中小学眼视光筛查系统,里面的简化模型就比较适合拿来做例子。如学校与学生,数目多也不过数千。这样的情况下,自然也是使用引用的方式更容易接受
> db.school.findOne()
{_id:ObjectId("cccc"),
name:"middle1",
location:"wenzhou",
students:[ObjectId("xxxx"),
ObjectId("yyyy"),
……
]
}
这里同样也引伸出一个“冗余”的问题,我们知道大多时候我们需要查询的数据属性数目是比较少的,比如对于学生而言,我们可能只需要知道他的身高体重,所以我们可以使用“冗余”思想简单修改刚才的集合成以下格式来应付
> db.school.findOne()
{ _id:ObjectId("cccc"),
name:"middle1",
location:"wenzhou",
students:[{ObjectId("xxxx"),name:"wddpct",height:233,weight:233},
{ObjectId("yyyy"),name:"wddmd",height:233,weight:233}
……
]
}
不过也要注意的一点是,这样每次更新 student 的信息时,不免又要对 school 中的冗余信息进行更新,所以也要结合具体场景使用
**
C. 1 – *(非常多)
**
地区和车牌的关系勉强属于此类,一个地区可能有几十上百万车牌,我们不可能再像刚才那样在 area 中加入所有的 license_id,不然可能光是单个文档大小就超过 MongoDB 的 16MB 限制了,而且对于查询也存在很大的负担。
这里我们可以直接套用关系型数据库中的外键思想,在 license 集合的末尾加入 area_id 就可以方便解决此类关系
> db.license.findOne()
{_id:ObjectId("cccc"),
license:"middle1",
area:ObjectId("xxxx")
}
当然,我们也可以对 area 进行进一步冗余,所以就不额外说明了。
**
D. * – *
**
对于多对多关系模型,可能又要祭出那句老话——“视具体情况而定”。不过一般情况下,它不过就是一对多关系的几个变种。一个基本的原则是考虑两边统一引用对方的 ObjectId,适当冗余部分信息。
除此以外,我们还可以从以下几个原则去考虑
- 两边的数量比(较大方更适合引用)
- 两边的更新频率比(较大方更适合引用)
- 两边的读取频率比(较大方更适合内嵌)
……
E. 通用建议
以下给出一张较通用的建议表,仅供参考
内嵌 | 引用 |
---|---|
子文档较小 | 子文档较大 |
数据不会定期更改 | 数据经常改变 |
最终数据一致即可 | 中间阶段数据也必须一致 |
文档数据小额增加 | 文档数据大幅增加 |
数据通常需要执行二次查询 | 数据通常不包含在查询结果中 |
快速读取 | 快速写入 |
更多 MongoDB 相关教程见以下内容:
CentOS 编译安装 MongoDB 与 mongoDB 的 php 扩展 http://www.linuxidc.com/Linux/2012-02/53833.htm
CentOS 6 使用 yum 安装 MongoDB 及服务器端配置 http://www.linuxidc.com/Linux/2012-08/68196.htm
Ubuntu 13.04 下安装 MongoDB2.4.3 http://www.linuxidc.com/Linux/2013-05/84227.htm
MongoDB 入门必读(概念与实战并重) http://www.linuxidc.com/Linux/2013-07/87105.htm
Ubunu 14.04 下 MongoDB 的安装指南 http://www.linuxidc.com/Linux/2014-08/105364.htm
《MongoDB 权威指南》(MongoDB: The Definitive Guide)英文文字版[PDF] http://www.linuxidc.com/Linux/2012-07/66735.htm
Nagios 监控 MongoDB 分片集群服务实战 http://www.linuxidc.com/Linux/2014-10/107826.htm
基于 CentOS 6.5 操作系统搭建 MongoDB 服务 http://www.linuxidc.com/Linux/2014-11/108900.htm
MongoDB 的详细介绍:请点这里
MongoDB 的下载地址:请点这里
本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-11/136756.htm