美文网首页
mongodb模型设计笔记!

mongodb模型设计笔记!

作者: DragonersLi | 来源:发表于2021-09-19 02:19 被阅读0次

原则上关系型数据库能做的事情,mongodb都可以,包括ACID事务。
优点:横向扩展能力,数据量或并发量增加时架构可以自动扩展;灵活模型,适合迭代开发数据模型多变场景;json数据结构,适合微服务rest API接口

mongodb 关系型数据库
亿级数据量 分库分表
高并发读写 需要优化
灵活表结构 关联表查询
跨地区集群 定制方案
分片集群 需要中间件
异构数据 使用EKV属性表
大宽表 性能受限
地理位置查询 比较完整地理位置 PG还可以,其它数据库麻烦
聚合计算 功能强大 group by能力有限

数据模型:由一组路由符号,文本组成的集合,用以准确表达信息,达到有效交流沟通的目的。
比如要建一个用户模型,不同行业或场景需求可能不同
数据模型设计的元素:
实体(Entity):描述业务的主要数据集合;谁,什么,何时,何地,为何,如何
属性(Attribute):描述实体里面的单个信息【属性字段可以存储数组,也可存储二进制图像】
关系(Relationship):描述实体与实体之间的数据规则,结构规则和引用规则

传统模型设计:从概念到逻辑再到物理【文档模型关系模型】,一个模型逐步细化的过程

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

mongodb文档模型设计的三个误区不需要模型设计用一个超级大文档组织所有数据不支持关联或事物
JSON文档模型设计:文档模型设计处于物理模型设计阶段,JSON文档模型通过内嵌数组或引用字段表示关系,文档模型设计不遵从第三范式,允许冗余
mongodb无模式由来:可以省略物理建模具体过程
文档模型设计原则:性能和开发易用,高并发,低延迟

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

文档模型设计三步曲:

`1.业务需求及逻辑模型,逻辑导向,基础建模【集合,字段,基础形状】`
建立基础文档模型
找到对象:根据概念模型或者业务需求推导出逻辑模型
明确关系:列出实体之间的关系(及基数)【一对一,一对多,多对多】
  一对一以内嵌对象为主,作为子文档形式(一个对象)。例外:内嵌导致文档大于16M。
  一对多以内嵌数组为主,用数组表示(多个对象)。例外:内嵌数组长度过大或不确定长度。导致文档大于16M等。
  多对多以冗余来实现,例外:内嵌数组长度过大或不确定长度。导致文档大于16M等。
进行建模:套用逻辑设计原则来决定内嵌方式
`2.技术需求(读写比例,方式及数量等),技术导向,工况细化【引用及关联】`
基于内嵌文档模型,根据业务需求,使用引用来避免性能瓶颈,使用冗余来优化访问性能
最频繁的数据查询模式 :
最常用的查询参数:
最频繁的数据写入模式:
读写操作比例
数据量大小
`3.经验和学习,模式导向,套用设计模式【最终模式】`
文档模型:无范式,无思维定式,充分发挥想象力
设计模式:实战总结的经验,设计技巧,快速应用

一个好的设计模式可以显著提升数据读写效率,降低资源需求

表现形式类 数据访问类 组织结构类
列转行 子集 预聚合
文档版本 近似处理 分桶
分桶设计模式:
场景:时序数据,物联网,智慧城市,智慧交通,心跳监控
痛点:数据点采集频繁,数据量大
设计模式方案及优点:利用文档内嵌数组,将一个时间段数据聚合到一个子文档中,大量减少文档数量和索引占用空间。可以把存储空间降低十倍且查询效率提升十倍

列转行设计模式:
场景:产品属性,多语言多国家等
痛点:文档中有很多类似字段,会用于组合查询搜索,需要建立很多索引
设计模式方案及优点:太多类似字段转化为数组存储为行,一个索引解决所有查询问题

版本字段设计模式:
场景:任何有版本衍变的数据库
痛点:文档模型格式多,无法知道其合理性,升级时候需要更新太多文档
设计模式方案及优点:增加一个版本号字段,快速过滤掉不需要升级的文档,升级时对不同版本文档做不同处理
 
近似计算:
场景:网页计数或不需要太精准排名统计等
痛点:写入太频繁,消耗系统资源
方案及优点:通过increment间隔N次再写入,大量减少写入请求

精准计算:
场景:业绩排名,游戏排名,电影排名或商品热销榜等精准统计
痛点:传统使用聚合计算,消耗系统资源,计算时间长
方案及优点:用预聚合字段,新增统计字段,每次更新同时更新统计值字段。例如:每次更新库存时,同时更新日,周,月,年销量

引用模式关联数组索引数组存储第三方key,类似于关系型设计用ID或者唯一键关联,主文档通过$lookup关联查询多张表能力
{$lookup:{from:"关联的文档名",localField:"本文档字段",foreignField:"关联的外键名",as:"别名"}}
引用设计使用场景内嵌文档太大,超过16M;内嵌文档或数组元素频繁修改,内嵌数组元素会持续增长无上限
引用设计的限制对引用的集合之间并无主外键检查;使用聚合框架的$lookup模仿关联查询,只支持left outer join;关联目标(from)不能是分片表

#用户模型【通过ID引用索引数组group和关联数组tag,值的类型要严格一致,否则引用失败】
db.users.insert([
{'id':'1','name':'lucy','group':[1,2],'tag':[{'id':1},{'id':2}]},
{'id':'2','name':'lily','group':[2,3],'tag':[{'id':2},{'id':3}]},
{'id':'3','name':'tom','group':[3,4],'tag':[{'id':3},{'id':4}]}, 
])
#用户分组模型
db.group.insert([
{'id':1,'name':'家人'},
{'id':2,'name':'亲戚'},
{'id':3,'name':'邻居'}, 
{'id':4,'name':'朋友'}, 
])
#用户标签模型
db.tag.insert([
{'id':1,'name':'看书'},
{'id':2,'name':'学习'},
{'id':3,'name':'游泳'}, 
{'id':4,'name':'健身'}, 
])

> db.group.find()
{ "_id" : ObjectId("6145be37baca65c61085c18b"), "id" : 1, "name" : "家人" }
{ "_id" : ObjectId("6145be37baca65c61085c18c"), "id" : 2, "name" : "亲戚" }
{ "_id" : ObjectId("6145be37baca65c61085c18d"), "id" : 3, "name" : "邻居" }
{ "_id" : ObjectId("6145be37baca65c61085c18e"), "id" : 4, "name" : "朋友" }
> db.tag.find()
{ "_id" : ObjectId("6145be3ebaca65c61085c18f"), "id" : 1, "name" : "看书" }
{ "_id" : ObjectId("6145be3ebaca65c61085c190"), "id" : 2, "name" : "学习" }
{ "_id" : ObjectId("6145be3ebaca65c61085c191"), "id" : 3, "name" : "游泳" }
{ "_id" : ObjectId("6145be3ebaca65c61085c192"), "id" : 4, "name" : "健身" }
> db.users.find()
{ "_id" : ObjectId("6145b697baca65c61085c180"), "id" : "1", "name" : "lucy", "group" : [ 1, 2 ], "tag" : [ { "id" : 1 }, { "id" : 2 } ] }
{ "_id" : ObjectId("6145b697baca65c61085c181"), "id" : "2", "name" : "lily", "group" : [ 2, 3 ], "tag" : [ { "id" : 2 }, { "id" : 3 } ] }
{ "_id" : ObjectId("6145b697baca65c61085c182"), "id" : "3", "name" : "tom", "group" : [ 3, 4 ], "tag" : [ { "id" : 3 }, { "id" : 4 } ] }

#只关联一个索引数组
> db.users.aggregate([ {$lookup:{from:'group',localField:'group',foreignField:'id',as:'group_info'}} ])
{ "_id" : ObjectId("6145b697baca65c61085c180"), "id" : "1", "name" : "lucy", "group" : [ 1, 2 ], "tag" : [ { "id" : 1 }, { "id" : 2 } ], "group_info" : [ { "_id" : ObjectId("6145be37baca65c61085c18b"), "id" : 1, "name" : "家人" }, { "_id" : ObjectId("6145be37baca65c61085c18c"), "id" : 2, "name" : "亲戚" } ] }
{ "_id" : ObjectId("6145b697baca65c61085c181"), "id" : "2", "name" : "lily", "group" : [ 2, 3 ], "tag" : [ { "id" : 2 }, { "id" : 3 } ], "group_info" : [ { "_id" : ObjectId("6145be37baca65c61085c18c"), "id" : 2, "name" : "亲戚" }, { "_id" : ObjectId("6145be37baca65c61085c18d"), "id" : 3, "name" : "邻居" } ] }
{ "_id" : ObjectId("6145b697baca65c61085c182"), "id" : "3", "name" : "tom", "group" : [ 3, 4 ], "tag" : [ { "id" : 3 }, { "id" : 4 } ], "group_info" : [ { "_id" : ObjectId("6145be37baca65c61085c18d"), "id" : 3, "name" : "邻居" }, { "_id" : ObjectId("6145be37baca65c61085c18e"), "id" : 4, "name" : "朋友" } ] }


#同时关联多个,索引数组和关联数组
> db.users.aggregate([
 {$lookup:{from:'group',localField:'group',foreignField:'id',as:'group_info'}},
 {$lookup:{from:'tag',localField:'tag.id',foreignField:'id',as:'tag_info'}}
 ])
{ "_id" : ObjectId("6145b697baca65c61085c180"), "id" : "1", "name" : "lucy", "group" : [ 1, 2 ], "tag" : [ { "id" : 1 }, { "id" : 2 } ], "group_info" : [ { "_id" : ObjectId("6145be37baca65c61085c18b"), "id" : 1, "name" : "家人" }, { "_id" : ObjectId("6145be37baca65c61085c18c"), "id" : 2, "name" : "亲戚" } ], "tag_info" : [ { "_id" : ObjectId("6145be3ebaca65c61085c18f"), "id" : 1, "name" : "看书" }, { "_id" : ObjectId("6145be3ebaca65c61085c190"), "id" : 2, "name" : "学习" } ] }
{ "_id" : ObjectId("6145b697baca65c61085c181"), "id" : "2", "name" : "lily", "group" : [ 2, 3 ], "tag" : [ { "id" : 2 }, { "id" : 3 } ], "group_info" : [ { "_id" : ObjectId("6145be37baca65c61085c18c"), "id" : 2, "name" : "亲戚" }, { "_id" : ObjectId("6145be37baca65c61085c18d"), "id" : 3, "name" : "邻居" } ], "tag_info" : [ { "_id" : ObjectId("6145be3ebaca65c61085c190"), "id" : 2, "name" : "学习" }, { "_id" : ObjectId("6145be3ebaca65c61085c191"), "id" : 3, "name" : "游泳" } ] }
{ "_id" : ObjectId("6145b697baca65c61085c182"), "id" : "3", "name" : "tom", "group" : [ 3, 4 ], "tag" : [ { "id" : 3 }, { "id" : 4 } ], "group_info" : [ { "_id" : ObjectId("6145be37baca65c61085c18d"), "id" : 3, "name" : "邻居" }, { "_id" : ObjectId("6145be37baca65c61085c18e"), "id" : 4, "name" : "朋友" } ], "tag_info" : [ { "_id" : ObjectId("6145be3ebaca65c61085c191"), "id" : 3, "name" : "游泳" }, { "_id" : ObjectId("6145be3ebaca65c61085c192"), "id" : 4, "name" : "健身" } ] }
> 

预聚合模式每次更新同时$inc更新要统计的字段值

> db.product.insert({'name':'test','sku':'A123','kc':1000,'day_sales':1,'week_sales':10,'month_sales':100,'year_sales':500})
WriteResult({ "nInserted" : 1 })
> db.product.find()
{ "_id" : ObjectId("61462b81fd0556d0fc4b9d78"), "name" : "test", "sku" : "A123", "kc" : 1000, "day_sales" : 1, "week_sales" : 10, "month_sales" : 100, "year_sales" : 500 }
> db.product.update({'name':'test'},{$inc:{'kc':-1,'day_sales':1,'week_sales':1,'month_sales':1,'year_sales':1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.product.find()
{ "_id" : ObjectId("61462b81fd0556d0fc4b9d78"), "name" : "test", "sku" : "A123", "kc" : 999, "day_sales" : 2, "week_sales" : 11, "month_sales" : 101, "year_sales" : 501 }

相关文章

网友评论

      本文标题:mongodb模型设计笔记!

      本文链接:https://www.haomeiwen.com/subject/zeizrxtx.html