【MongoDB】二、模型设计及模式套用

模型设计基础

数据模型的三要素:

  • 实体Entity:描述业务的主要数据集合

邮箱、电话、地址

  • 属性Attribute:描述实体里面的单个信息

邮箱下的类型和地址、电话类型和号码、地址下的省、市、县等

  • 关系Relationship:描述实体间的数据规则,结构规则:1-N,N-1,N-N

一个联系人可以有一个头像(1-1)

一个联系人可以有多个地址(1-N)

一个联系人可以属于多个组,一个组可以有多个联系人(N-N)

数据模型的三层深度:

概念模型CDM

逻辑模型LDM

物理模型PDM

目的

描述业务系统要管理的对象

基于概念模型,详细列出所有实体、实体的属性及关系

根据逻辑模型,结合数据库的物理结构,设计具体的表结构,字段列表及主外键

特点

用概念名词来描述现实中的实体及业务规则,如“联系人”

基于业务的描述,和数据库无关

技术实现细节,和具体的数据库类型相关

主要使用者

用户需求分析师

需求分析师、架构师、开发者

开发者、数据库管理员(DBA)

关系模型的设计原则——第三范式原则:

数据在库里尽量不可能存在冗余

JSON文档模型设计

文档模型设计处于物理模型设计PDM,不像关系模型需要遵从第三范式,允许冗余

设计原则:

  • 性能:是否能支撑高并发,低延迟的读写

  • 开发易用:程序开发中是否容易使用

关系数据库

JSON文档模型

模型设计层次

概念模型→逻辑模型→物理模型

概念模型→逻辑模型

模型实体

集合

模型属性

字段

模型关系

关联关系,主外键

内嵌数组,引用字段

文档模型设计三步:

graph LR
A1([业务需求
逻辑模型]) A2(基础建模) A3("集合
字段
基础形状") B1([技术需求
读写比例
方式及数量]) B2(工况细化) B3(引用及关联) C1([经验和学习]) C2(套用设计模式) C3(最终模式) A1 --逻辑导向--> A2 --> A3 B1 --技术导向--> B2 --> B3 C1 --模式导向--> C2 --> C3

基础建模

graph LR
A1([业务需求
逻辑模型]) A2(基础建模) A3("集合
字段
基础形状") A1 --逻辑导向--> A2 --> A3
  1. 根据概念模型/业务需求推导出逻辑模型→找到对象

  1. 列出实体间的关系(及基数)→明确关系

  1. 套用逻辑设计原则来决定内嵌方式→进行建模

  1. 完成基础模型构建

  • 1-1关系建模:以内嵌为主,作为子文档形式或直接在顶级

  • 例外:

  • 内嵌后导致文档大小超过16MB

  • 1-N关系建模:以内嵌为主

  • 例外:

  • 内嵌后导致文档大小超过16MB

  • 数组长度太大(数万↑)

  • 数组长度不确定

  • N-N关系建模:不需要映射表,用内嵌数组表示一对多,通过冗余来实现N-N

  • 例外:

  • 内嵌后导致文档大小超过16MB

  • 数组长度太大(数万↑)

  • 数组长度不确定

工况细化

graph LR
B1([技术需求
读写比例
方式及数量]) B2(工况细化) B3(引用及关联) B1 --技术导向--> B2 --> B3

根据下述问题细化:

  • 最频繁的数据查询模式

  • 最常用的查询参数

  • 最频繁的数据写入模式

  • 读写操作的比例

  • 数据量的大小

引用:避免性能瓶颈

  • 何时使用?

  • 内嵌文档太大,数组MB或超过16MB

  • 例:用户信息存在集合Contacts中,通过引用的方式获取存在集合Contact_Portrait中的头像。在用户查询时先查询基础信息,点击查看头像时再根据引用查询头像,提高查询效率

  • 内嵌文档/数组数据会频繁修改

  • 内嵌数组会持续增长并且没有封顶

  • 引用设计的限制

  • 使用引用的集合间没有主外键检查

  • 使用聚合框架的$lookup来模仿关联查询

  • $lookup只支持left outer join

  • $lookup的关联目标(from)不能是分片表

冗余:优化访问性能

模式套用

graph LR
C1([经验和学习])
C2(套用设计模式)
C3(最终模式)
C1 --模式导向--> C2 --> C3

一个好的设计模式可以:

  1. 提升数据读写效率

  1. 降低资源需求

设计模式集锦

列转行

某个电影需要记录不同国家的发布时间,需要很多字段和索引

{
title: "Dunkirk",
...
release_USA: "2017/07/23",
release_UK: "2017/08/01",
release_France: "2017/08/01",
release_Festival_San_Jose:
"2017/07/22"
}

使用列转行:

  • 字段数据变少

  • 通过一个索引支持所有国家的查询

  • db.movies.createIndex({“releases.country”:1, “releases.date”:1})

{
title: "Dunkirk",
...
releases: [
{ country: “USA”, date:”2017/07/23”},
{ country: “UK”, date:”2017/08/01”}
]
}

场景

痛点

设计模式方案及优点

产品属性'color','size'... 多语言(多国家)属性

文档中有很多类似的字段 会用于组合查询搜索,需要建很多索引

转化为数组,一个索引解决所有查询问题

版本字段

版本二的文档比版本一的文档多了一个字段,可以通过增加一个版本字段的方式管理文档

场景

痛点

设计模式方案及优点

任何有版本衍变的数据库

文档模型格式多,无法知其合理性 升级时需要更新太多文档

增加一个版本号字段 快速过滤掉不需要升级的文档 升级时对不同版本的文档做不同处理

近似计算

统计网页点击流量时,每访问一个页面都会产生一次数据库计数更新操作,统计数字准确性并不十分重要

可以通过近似计算的方式减少写入数据库的次数

if random(0,9) == 0
	increment by 10

场景

痛点

设计模式方案及优点

网页计数 各种结果不需要准确的排名

写入太频繁,消耗系统资源

间隔写入,每隔n次写入一次 大量减少写入需求

预聚合

业绩排名、电影观看排行等需要精确统计的数字无法使用近似计算

传统解决方案是通过聚合计算来得出结果,但是需要消耗较多的资源,且聚合计算需要较长的时间

要计算某个商品销量:

{
product: ”Bike",
sku: “abc123456”,
quantitiy: 20394,
daily_sales: 40,
weekly_sales: 302,
monthly_sales: 1419
}

可以使用预聚合,加几个字段,在每次更新时顺便一起更新这些字段

db.inventory.update({_id:123},
{$inc: {
quantity: -1, 
daily_sales: 1, 
weekly_sales: 1,
monthly_sales: 1,
}
}
)

场景

痛点

设计模式方案及优点

准确排名

统计计算耗时,计算时间长

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

你可能感兴趣的:(MongoDB,mongodb,nosql)