数据模型
什么是数据模型?
1)数据模型是一组由符号、文本组成的集合,用以准确表达信息,达到有效交流、沟通 的目的。
2)Steve Hoberman 霍伯曼. 数据建模经典教程
数据模型设计的元素
1)实体 Entity
2)属性 Attribute
3)关系 Relationship
传统模型设计:从概念到逻辑到物理
概念模型 CDM | 逻辑模型 LDM | 物理模型 PDM | |
---|---|---|---|
目的 | 描述业务系统要管理的对象 | 基于概念模型,详细列出 所有实体、实体的属性及 关系 | 根据逻辑模型,结合数据库 的物理结构,设计具体的表 结构,字段列表及主外键 |
特点 | 用概念名词来描述现实中 的实体及业务规则,如 “联系人” | 基于业务的描述 和数据库无关 | 技术实现细节 和具体的数据库类型相关 |
主要使用者 | 用户 需求分析师 | 需求分析师 架构师及开发者 | 开发者 DBA |
MongoDB 文档模型设计的三个误区
- 不需要模型设计
- MongoDB 应该用一个超级大文档来组织所有数据
- MongoDB 不支持关联或者事务
关于 JSON 文档模型设计
- 文档模型设计处于是物理模型设计阶段 (PDM)
- JSON 文档模型通过内嵌数组或引用字段来表示关系
- 文档模型设计不遵从第三范式,允许冗余。
为什么人们都说 MongoDB 是无模式?
- 严格来说,MongoDB 同样需要概念/逻辑建模
- 文档模型设计的物理层结构可以和逻辑层类似
- MongoDB 无模式由来: 可以省略物理建模的具体过程
关系模型 vs 文档模型
文档模型的设计原则:性能和易用
关系数据库 | JSON 文档模型 | |
---|---|---|
模型设计层次 | 概念模型 逻辑模型 物理模型 | 概念模型 逻辑模型 |
模型实体 | 表 | 集合 |
模型属性 | 列 | 字段 |
模型关系 | 关联关系,主外键 | 内嵌数组,引用字段 |
文档模型设计
第一步: 建立基础文档模型
- 根据概念模型或者业务需求推导出逻辑模型 – 找到对象
- 列出实体之间的关系(及基数) - 明确关系
- 套用逻辑设计原则来决定内嵌方式 – 进行建模
- 完成基础模型构建
基础建模小结
- 90:10 规则: 大部分时候你会使用内嵌来表示 1-1,1-N,N-N
- 内嵌类似于预先聚合(关联)
- 内嵌后对读操作通常有优势(减少关联)
第二步: 根据读写工况细化
- 最频繁的数据查询模式
- 最常用的查询参数
- 最频繁的数据写入模式
- 读写操作的比例
- 数据量的大小
基于内嵌的文档模型,根据业务需求,
1)使用引用来避免性能瓶颈
2)使用冗余来优化访问性能
引用模式下的关联查询
Contacts
name: "TJ Tang",
company: ”TAPDATA"
group_ids: [1,2,3...]
Groups
groups_id:1
name:“Friends”
db.contacts.aggregate([
{
$lookup:
{
from: "groups",
localField: "group_ids",
foreignField: "group_id",
as: "groups"
}
}
])
什么时候该使用引用方式?
- 内嵌文档太大,数 MB 或者超过 16MB
- 内嵌文档或数组元素会频繁修改
- 内嵌数组元素会持续增长并且没有封顶
MongoDB 引用设计的限制
-
MongoDB 对使用引用的集合之间并无主外键检查
-
MongoDB 使用聚合框架的 $lookup 来模仿关联查询
-
$lookup 只支持 left outer join
-
$lookup 的关联目标(from)不能是分片表
第三步:套用设计模式
- 文档模型: 无范式,无思维定式,充分发挥想象力
- 设计模式: 实战过屡试不爽的设计技巧,快速应用
举例:一个 IoT 场景的分桶设计模式,可以帮助把存储空间降低 10 倍并且查询效率提 升数十倍.
模式集锦
分桶模式
场景 | 痛点 | 设计模式的方案及优点 |
---|---|---|
时序数据 物联网 智慧城市 智慧交通 | 数据点采集频繁,数据量太多 | 利用文档内嵌数组,将一个时 间段的数据聚合到一个文档里。 大量减少文档数量 大量减少索引占用空间 |
列传行模式
场景 | 痛点 | 设计模式方案及优点 |
---|---|---|
产品属性 ‘color’, ‘size’, ‘dimensions’, … 多语言(多国家)属性 | 文档中有很多类似的字段 会用于组合查询搜索,需要见 很多索引 | 转化为数组,一个索引解决所 有查询问题 |
版本字段
2019.01 版本1.0
{
"_id" : ObjectId("5de26f197edd62c5d388babb"),
"name" : "TJ",
"company" : "Tapdata",
}
2019.03 版本2.0
{
"_id" : ObjectId("5de26f197edd62c5d388babb"),
"name" : "TJ",
"company" : "Tapdata",
"wechat": "abc123",
"schema_version": "2.0"
}
场景 | 痛点 | 设计模式方案及优点 |
---|---|---|
任何有版本衍变的数据库 | 文档模型格式多,无法知道其 合理性升级时候需要更新太多文档 | 增加一个版本号字段 快速过滤掉不需要升级的文档 |
近似计算模式
问题: 统计网页点击流量
每访问一个页面都会产生一次数据库计数 更新操作
统计数字准确性并不十分重要
解决方案: 用近似计算
每隔10 (X)次写一次
场景 | 痛点 | 设计模式方案及优点 |
---|---|---|
网页计数 各种结果不需要准确的排名 | 写入太频繁,消耗系统资源 | 间隔写入,每隔10次或者100次 大量减少写入需求 |
预聚合模式
问题: 业绩排名,游戏排名,商品统计等精确统计
热销榜: 某个商品今天卖了多少,这个星期卖了多少,这个月卖了多少?
电影排行: 观影者,场次统计
传统解决方案: 通过聚合计算
痛点: 消耗资源多,聚合计算时间长
解决方案: 用预聚合字段
{
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,
}
})
场景 | 痛点 | 设计模式方案及优点 |
---|---|---|
准确排名 排行榜 | 统计计算耗时,计算时间长 | 模型中直接增加统计字段 每次更新数据时候同时更新统计值 |
「真诚赞赏,手留余香」
请我喝杯咖啡?
使用微信扫描二维码完成支付
