MongoDB学习5:模型设计和设计模式

1.数据模型

  1.1 什么是数据模型?

  数据模型是一组由符号、文本组成的集合,用以准确表达信息,达到有效交流、沟通的目的

  1.2 数据模型设计元素

  • 实体 Entity
    • 描述业务的主要数据集合
  • 属性 Attribute
    • 描述实体里面的单个属性
  • 关系 Relationship
    • 描述实体与实体之间的数据规则
    • 结构规则:1-N N-1 N-N
    • 引用规则:比如电话号码不能单独存在,必须依赖于具体的人

2.JSON文档模型设计特点

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

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

  2.2 为什么说MongoDB是无模式的

  • 严格来说,MongoDB同样需要概念/逻辑建模
  • 文档模型设计的物理层结构可以和逻辑层类似
  • 可以省略物理建模的具体过程

  2.3 文档设计模式原则:性能和易用

  2.4 关系模型 vs 文档模型

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

3.MongoDB文档模型设计三部曲

  3.1 建立基础文档模型

  • 根据概念模型或者业务需求推导出逻辑模型 -找到对象

  • 列出实体之间的关系 -明确关系

  • 套用逻辑设计原则来决定内嵌方式 -进行建模

  • 完成基础模型构建

  • 一个联系人管理应用的例子

    • 1.找到对象
      -- Contacts
      -- Group
      -- Address
      -- Portraits
    • 2.明确关系
      -- 一个联系人有1个头像 (1-1)
      -- 一个联系人可以有多个地址 (1-N)
      -- 一个联系人可以属于多个组,一个组可以有多个联系人(N-N)
    • 3.关系建模(1-1):Portraits
      -- 基本原则:一对一关系以内嵌为主,作为子文档形式或者直接在顶级,不涉及到数据冗余
      -- 例外情况:如果内嵌后导致文档大小超过16MB
    • 4.关系建模(1-N):Address
      -- 基本原则:一对多关系同样以内嵌为主,用数组来表示一对多,不涉及到数据冗余
      -- 例外情况:内嵌后导致文档大小超过16MB、数组长度太大(数万或更多)、数组长度不确定
    • 5.关系建模(N-N):Groups
      -- 基本原则:不需要映射表,一般用内嵌数组来表示一对多,通过冗余来实现N-N
      -- 例外情况:内嵌后导致文档大小超过16MB、数组长度太大(数万或更多)、数组长度不确定

  3.2 根据读写工况细化

  • 联系管理应用的分组需求
    Q : 假如有千万级联系人;需要频繁变动分组的信息,比如增加分组及修改名称及描述以及营销状态;一个分组有百万级联系人,如何解决?
    A : Group使用单独的集合

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

    • 内嵌文档太大,数MB或者超过16MB
    • 内嵌文档或数组元素会频繁修改
    • 内嵌数组元素会持续增长并且没有封顶
  • MongoDB引用设计的限制

    • MongoDB对使用引用的集合无主外键检查(需要程序自行判断)
    • MongoDB使用聚合框架的 $lookup 来模仿关联查询
    • $lookup 只支持 left outer join
    • $lookup 的关联目标(from)不能是分片表
db.contacts.aggregate([
 {
   $lookup:{
     from:"groups",   #外联表
     localField:"group_ids",    #外键字段
     foreignField:"group_id",    #外联表主键
     as:"groups"     #查询结果
   }
 }
])

  3.3 套用设计模型

  • 物联网场景下的海量数据处理 - 飞机监控数据
    -- 要求记录飞机的实时位置,假设有10万架飞机、1年的数据、每分钟一条
    -- 如果每架飞机每分钟都往数据库写入一条数据,那么数据量将非常庞大,如何解决呢?可以使用分桶设计解决:一个文档保存一架飞机一小时的数据
每分钟1个文档 每小时1个文档
文档条数 52.6B 876M
索引大小(_id index \ {ts:1,deviceId:1}) 6364GB(1468GB \ 4895GB) 106GB(24.5GB \ 81.6GB)
文档平均大小 92 Bytes 758 Bytes
数据大小 4503GB 618GB
  • 大文档,很多字段,很多索引
    -- 比如一个表中存在各种各样的名字(chineseName,englishName,franchName...),而且每个名字会频繁的查询。可以使用列转行,将多个相同的列转化为一个数组(names:{ chinese:'',english:'',franch:'' }

  • 模型灵活了,如何管理文档的不同版本?
    -- 增加一个版本字段

  • 统计网页流量点击
    -- 如果每次点击页面都产生一个计数更新操作,那么数据库将大量由此操作占据了
    -- 这种统计数字准确性并不十分重要,可以使用近似计算优化:每10次操作计数一次写入库数值10

if random(0,9) == 0
  increment by 10
  • 业绩排名、游戏排名、商品销售统计等精确统计
    -- 如某个商品今天卖了多少、本周卖了多少、本月卖了多少
    -- 传统解决方案是通过聚合计算,但是消耗资源多,聚合计算时间长;使用聚合字段
{
  product:"洗衣服",
  sku:"10000",
  price:23.99,
  stock:9999,
  daily_sales:10,
  weekly_sales:100,
  monthly_sales:700
}

db.products.update({_id:123,{
  $inc:{
    stock:-1,
    daily_sales:1,
    weekly_sales:1,
    monthly_sales:1
  }
}})
  • 模式小结
模式 场景 痛点 设计模式的方案及优点
分桶 时序数据(物联网、智慧城市、智慧交通) 数据点采集频繁,数据量太多 利用文档内嵌数组,将一个时间段的数据聚合到一个文档里
大量减少文档数量
大量减少索引占用空间
列转行 产品属性(color、size...)
多语言(多国家)属性
文档中有很多类似的字段
会用于组合查询搜索,需要建立很多索引
转化为数组,一个索引解决所有查询问题
版本字段 任何有版本衍变的数据库 文档模型格式多,无法知道其合理性
升级时需要更新太多文档
增加一个版本号字段
快速过滤掉不需要升级的文档
升级时对不同版本的文档做不同的处理
近似计算 网页计数,各种结果不需要准确的排名 写入太频繁,消耗系统资源 间隔写入,每隔10次或100次写入
大量减少写入操作
预聚合 准确排名、排行榜 统计计算耗时,计算时间长 模型中直接增加统计字段
每次更新数据的同时更新统计值
posted @ 2020-08-12 22:16  等一个,晴天  阅读(362)  评论(0编辑  收藏  举报