以下文字总结来源于:极客时间 唐建法的《MongoDB高手课》。
数据模型就是通过创建一个逻辑化、物理化的模型,来提供一个同一个层面交流的目的。
- 描述业务的主要数据集合
- 谁,什么,何时,何地,为何,如何
- 描述实体里面的单个信息
- 描述实体与实体之间的数据规则
- 解构规则:1-N,N-1,N-N
- 引用规则:
模型概念 CDM | 逻辑模型 LDM | 物理模型 PDM | |
---|---|---|---|
目的 | 描述业务系统要管理的对象 | 基于概念模型,详细列出所有实体、实体的属性及关系 | 根据逻辑模型,结合数据库的物理结构,设计具体的表结构,字段列表及主外键 |
特点 | 用概念名词来描述现实中的实体及业务规则,例如"联系人" | 基于业务的描述,和数据库无关 | 技术实现细节和具体的数据库类型相关 |
主要使用者 | 用户需求分析师 | 需求分析师、架构师及开发者 | 开发者DBA |
注意:
在关系型数据库设计中,遵循第三范式原则:数据库在库里尽量不可能存在冗余。 例如“联系人地址”中,要将地址里的省份、城市、区县进行单独存储,因为多个联系人地址中该部分为共有。
- 不需要设计模型
- MongoDB 应该用一个超级大的文档来组织所有数据
- MongoDB 不支持关联或事务
- 文档模型设计处于物理模型设计阶段(PDM)
- JSON 文档O型通过内嵌数组或引用字段来表示关系
- 文档模型设计不遵循第三范式,允许冗余
- 严格来说,MongoDB 同样需要概念/逻辑建模
- 文档模型设计的物理层结构可以和逻辑层类似
- MongoDB无模式由来:可以省略物理建模的具体过程
- 性能 Performance
- 开发易用 Ease of Development
| | 关系数据库 | JSON文档模型 |
| ---- | ---- | ---- | ---- |
| 模型设计层次 | 概念模型、逻辑模型、物理模型 | 概念模型、逻辑模型 |
| 模型实体 | 表 | 集合 |
| 模型属性 | 列 | 字段 |
| 模型关系 | 关联关系,主外键 | 内嵌数组,引用字段 |
步骤 | 说明 |
---|---|
第1步 | 业务需求及逻辑模型 ——> 逻辑导向 ——> 基础建模 ——> {集合、字段、基础形状} |
第2步 | 技术需求、读写比例、方式及数量 ——> 技术导向 ——> 工况细化 ——> {引用及关联} |
第3步 | 经验和学习 ——> 模式导向 ——> 套用设计模式 ——> {最终模式} |
注意:
以上所谓3步曲,本质上是 MongoDB 文档模型设计优化进阶的三个阶段。
- 根据概念模型或者业务需求推导出逻辑模型 ——> 找到对象
- 列出实体之间的关系(及基数) ——> 明确关系
- 套用逻辑设计原则来决定内嵌方式 ——> 进行建模
- 完成基础模型构建
业务需求及逻辑模型 ——> 逻辑导向 ——> 基础建模 ——> {集合、字段、基础形状}
注意:
MongoDB单个文档大小不能超过16MB
读写工况场景:
- 最频繁的数据查询模式
- 最常用的查询参数
- 最频繁的数据写入模式
- 读写操作的比例
- 数据量的大小
基于内嵌的文档模型,根据业务需求:
- 使用引用来避免性能瓶颈
- 使用冗余来优化访问性能
技术需求、读写比例、方式及数量 ——> 技术导向 ——> 工况细化 ——> {引用及关联}
- 内嵌文档太大,数量MB或者超过16MB
- 内嵌文档或数组元素会频繁修改
- 内嵌数组元素会持续增长并且没有封顶
- MongoDB对使用引用的集合之间并无主外键检查
- MongoDB使用聚合框架的 $lookup 来模仿关联查询
- $lookup 只支持 left outer join
- $lookup 的关联目标(from)不能是分片表
文档模型:无范式,无思维定式,充分发挥想象力
设计模式:实战过屡试不爽的设计技巧,快速应用
举例:一个loT(物联网)场景的分桶设计模式,可以帮助把储存空间降低10倍并且查询效率提升数10倍。
分桶设计模式:可以将每分钟为一条数据通过文档内嵌数组,改为每一小时为一条数据。减少文档数量,减少索引占用空间。
经验和学习 ——> 模式导向 ——> 套用设计模式 ——> {最终模式}
表现形式类 | 数据访问类 | 组织结构类 |
---|---|---|
列转行 | 子集 | 预聚合 |
文档版本 | 近似处理 | 分桶 |
场景:大文档,很多字段,很多索引 举例:一部电影在几十个国家的不同上映日期,例如美国上映日期对应的字段 release_USE:"2020/06/02"、在中轨上映日期对应的字段 release_CN:"2020/06/01" ...
解决方案:列转行
- 新建一个字段 releases 用来储存所有国家上映日期数据,属性值为数组
- 属性值数组中的元素包含国家和日期
最终数据结构:
{
releases:[
{country:"USA",date:"2020/06/02"},
{country:"CN",date:"2020/06/01"}
]
}
修改之后的文档模型,总体字段变少了,国家和上映日期都被储存在 releases 这1个字段中,利于提高查询效率。
列转行设计模式优点:将多个字段转化为一个字段上的数组元素,一个索引解决所有查询问题。
场景:文档模型灵活了,如何管理文档不同版本?
举例:6月份以后需要在数据中新增一个字段 wechat,而这个字段是在之前的数据中不存在的。而预计下个月还会要新增别的字段,最终导致一个集合中的数据字段很多地方不相同。
解决方案:文档版本
- 给数据添加一个版本号字段
- 当每次要发生数据结构形态变化时,设定对应的文档版本
- 在读写数据时,通过文档版本字段来获取对应的文档结构
文档版本设计模式方案优点:通过增加一个版本号字段,可以区分不同文档所具有的数据格式。数据库升级时可以快速过滤掉不需要升级的文档,或升级时对不同版本的文档做不同的处理。
问题:数据写入量大,读取量小,写入太频繁消耗系统资源
举例:统计网页点击流量,每访问一个页面都会产生一次数据库技术更新操作。统计数字准确性要求并不是特别重要,不需要特别精确
解决方案:近似计算
- 原本每访问一次都需要精确统计+1,现在改为近似计算
- 每次访问页面,执行一个更新操作,随机产生一个0-9的随机数,若随机数等于0(也可以是其他数字),则统计+10
近似计算设计模式优点:间隔写入,每隔10次或100次写入一次,每次写入统计+10或+100,大量减少写入次数。
注意:
近似计算的前提是对统计要求不需要那么精准,例如网页流量统计,若需要精准统计则近似计算无法满足。
问题:排名,商品统计等精确统计
举例:热销榜(日/周/月)、电影排行榜
传统解决方案:通过聚合计算
缺点:消耗资源多,聚合计算时间长
解决方案:用预聚合字段
- 模型中直接增加统计字段
- 每次更新数据时,同时更新统计值
注意:
预聚合使用 $inc