AurorePaladin / AurorePaladin.github.io

个人主页。同时也通过 Issues 记录学习笔记
5 stars 1 forks source link

MongoDB文档模型设计 #67

Open AurorePaladin opened 2 years ago

AurorePaladin commented 2 years ago

MongoDB文档模型设计

什么是数据模型?

数据模型就是通过创建一个逻辑化、物理化的模型,来提供一个同一个层面交流的目的。

数据模型设计的元素

实体(Entity)

属性(Attribute)

关系(Relationship)

数据模型设计基础

传统模型设计:从概念到逻辑到物理

模型概念 CDM 逻辑模型 LDM 物理模型 PDM
目的 描述业务系统要管理的对象 基于概念模型,详细列出所有实体、实体的属性及关系 根据逻辑模型,结合数据库的物理结构,设计具体的表结构,字段列表及主外键
特点 用概念名词来描述现实中的实体及业务规则,例如"联系人" 基于业务的描述,和数据库无关 技术实现细节和具体的数据库类型相关
主要使用者 用户需求分析师 需求分析师、架构师及开发者 开发者DBA

注意:
在关系型数据库设计中,遵循第三范式原则:数据库在库里尽量不可能存在冗余。 例如“联系人地址”中,要将地址里的省份、城市、区县进行单独存储,因为多个联系人地址中该部分为共有。

MongoDB文档模型设计的三个误区

  1. 不需要设计模型
  2. MongoDB 应该用一个超级大的文档来组织所有数据
  3. MongoDB 不支持关联或事务

关于JSON 文档模型设计

为什么人们都说MongodDB是无模式?

文档模型的设计原则:性能和易用

关系模型 VS 文档模型

关系数据库 JSON文档模型
模型设计层次 概念模型、逻辑模型、物理模型 概念模型、逻辑模型
模型实体 集合
模型属性 字段
模型关系 关联关系,主外键 内嵌数组,引用字段

MongoDB文档模型设计三步曲

步骤 说明
第1步 业务需求及逻辑模型 ——> 逻辑导向 ——> 基础建模 ——> {集合、字段、基础形状}
第2步 技术需求、读写比例、方式及数量 ——> 技术导向 ——> 工况细化 ——> {引用及关联}
第3步 经验和学习 ——> 模式导向 ——> 套用设计模式 ——> {最终模式}

注意:
以上所谓3步曲,本质上是 MongoDB 文档模型设计优化进阶的三个阶段。

第1步:建立基础文档模型

  1. 根据概念模型或者业务需求推导出逻辑模型 ——> 找到对象
  2. 列出实体之间的关系(及基数) ——> 明确关系
  3. 套用逻辑设计原则来决定内嵌方式 ——> 进行建模
  4. 完成基础模型构建

业务需求及逻辑模型 ——> 逻辑导向 ——> 基础建模 ——> {集合、字段、基础形状}

注意:
MongoDB单个文档大小不能超过16MB

第2步:根据读写工况细化

读写工况场景:

基于内嵌的文档模型,根据业务需求:

  1. 使用引用来避免性能瓶颈
  2. 使用冗余来优化访问性能

技术需求、读写比例、方式及数量 ——> 技术导向 ——> 工况细化 ——> {引用及关联}

什么时候应该使用引用方式?

  1. 内嵌文档太大,数量MB或者超过16MB
  2. 内嵌文档或数组元素会频繁修改
  3. 内嵌数组元素会持续增长并且没有封顶

MongoDB引用设计的显示

  1. MongoDB对使用引用的集合之间并无主外键检查
  2. MongoDB使用聚合框架的 $lookup 来模仿关联查询
  3. $lookup 只支持 left outer join
  4. $lookup 的关联目标(from)不能是分片表

第3步:套用设计模式

文档模型:无范式,无思维定式,充分发挥想象力
设计模式:实战过屡试不爽的设计技巧,快速应用

举例:一个loT(物联网)场景的分桶设计模式,可以帮助把储存空间降低10倍并且查询效率提升数10倍。
分桶设计模式:可以将每分钟为一条数据通过文档内嵌数组,改为每一小时为一条数据。减少文档数量,减少索引占用空间。

经验和学习 ——> 模式导向 ——> 套用设计模式 ——> {最终模式}

MongoDB设计模式

表现形式类 数据访问类 组织结构类
列转行 子集 预聚合
文档版本 近似处理 分桶

MongoDB设计模式集锦

列转行

场景:大文档,很多字段,很多索引 举例:一部电影在几十个国家的不同上映日期,例如美国上映日期对应的字段 release_USE:"2020/06/02"、在中轨上映日期对应的字段 release_CN:"2020/06/01" ...

解决方案:列转行

  1. 新建一个字段 releases 用来储存所有国家上映日期数据,属性值为数组
  2. 属性值数组中的元素包含国家和日期

最终数据结构:

{
  releases:[
    {country:"USA",date:"2020/06/02"},
    {country:"CN",date:"2020/06/01"}
  ]
}

修改之后的文档模型,总体字段变少了,国家和上映日期都被储存在 releases 这1个字段中,利于提高查询效率。

列转行设计模式优点:将多个字段转化为一个字段上的数组元素,一个索引解决所有查询问题。

文档版本

场景:文档模型灵活了,如何管理文档不同版本?
举例:6月份以后需要在数据中新增一个字段 wechat,而这个字段是在之前的数据中不存在的。而预计下个月还会要新增别的字段,最终导致一个集合中的数据字段很多地方不相同。

解决方案:文档版本

  1. 给数据添加一个版本号字段
  2. 当每次要发生数据结构形态变化时,设定对应的文档版本
  3. 在读写数据时,通过文档版本字段来获取对应的文档结构

文档版本设计模式方案优点:通过增加一个版本号字段,可以区分不同文档所具有的数据格式。数据库升级时可以快速过滤掉不需要升级的文档,或升级时对不同版本的文档做不同的处理。

近似计算

问题:数据写入量大,读取量小,写入太频繁消耗系统资源
举例:统计网页点击流量,每访问一个页面都会产生一次数据库技术更新操作。统计数字准确性要求并不是特别重要,不需要特别精确

解决方案:近似计算

  1. 原本每访问一次都需要精确统计+1,现在改为近似计算
  2. 每次访问页面,执行一个更新操作,随机产生一个0-9的随机数,若随机数等于0(也可以是其他数字),则统计+10

近似计算设计模式优点:间隔写入,每隔10次或100次写入一次,每次写入统计+10或+100,大量减少写入次数。

注意:
近似计算的前提是对统计要求不需要那么精准,例如网页流量统计,若需要精准统计则近似计算无法满足。

问题:排名,商品统计等精确统计
举例:热销榜(日/周/月)、电影排行榜

传统解决方案:通过聚合计算
缺点:消耗资源多,聚合计算时间长

解决方案:用预聚合字段

  1. 模型中直接增加统计字段
  2. 每次更新数据时,同时更新统计值

注意:
预聚合使用 $inc