原则上关系型数据库能做的事情,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 }
网友评论