设计历来就是个挑战。面试
当咱们第一次接触数据库,学习数据库基础理论时,都须要学习范式,老师也一再强调范式是设计的基础。范式是这门课程中的重要部分,在期末考试中也必定是个重要考点。若是咱们当年大学挂科了,说不定就是范式这道题没有作好。毕业后,当咱们面试时,每每也有关于表设计方面拷问。数据库
不少时候,咱们错误地认为,花费大量时间用在设计上,问题根源在于关系数据库(RDBMS),在于二维表及其之间的联系组成的一个数据组织。而真实的环境中,咱们正在大量使用noSQL或者NewSQL,按照目前的趋势(DB-Engines Ranking 得分),未来还会愈来愈广泛。选用noSQL或者NewSQL 就不须要模式设计了。而且,随着公司、行业数字化程度的加深,智能化触角逐渐延伸,数据量愈来愈大,结构愈来愈复杂。 例如如今很火的IOT行业,复杂的业务信息、多样的传输协议、不断升级的传感器,都须要灵活的数据模型来应对。在这种呼唤声中,MongoDB闪亮登场了。MongoDB支持灵活的数据模型。主要体如今如下2点:设计模式
(1)自由模式,无需提早声明、建立表结构,即不用先建立表、添加字段,而后才能够Insert数据。默认状况下MongoDB无需这样操做,除非开启了模式验证。数组
(2)键值类型自由,MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。字段值能够包含其余文档,数组及文档数组。数据结构
MongoDB不须要模式设计时错误的,其实面对复杂的结构对象,模式的自由带来更大的挑战。ide
模式的自由是对数据insert这个动做而言,它去除不少限制了,能够快速讲对象的存进来,而且易于扩展。可是不必定就会带来好的查询性能,好的查询性能还要来自于好的模式设计、来自于好的集合文档的设计。性能
MongoDB能够将模式设计划分为内嵌模式(Embedded)和 引用模式(References)学习
简单来说,内嵌模式就是将关联数据,放在一个文档中。例如如下员工信息采用内嵌模式了而存储在了一个文档中:优化
引用模式是将数据存储在不一样集合的文档中,而经过关系数据进行关联。例如,这里采用引用模式将员工信息存储在了3个文档中,基本信息一个文档,联系方式一个文档,登陆权限放在了一个文档中。每一个文档以前经过user_id来关联。网站
下面咱们经过一些业务场景,一些具体的案例,来分析、品味一下MongoDB模式设计的选择。
假如如今咱们描述来顾客(patron)和顾客的地址(address),其ER图以下:
咱们能够将patron和address设计成两个集合(collection,相似于RDBMS数据库中的table),其具体信息以下:
patron 集合
{
_id: "joe",
name: "Joe Bookreader"
}
address 集合
{
patron_id: "joe",
street: "123 Fake Street",
city: "Faketon",
state: "MA",
zip: "12345"
}
在设计address 集合时,内嵌了patron集合的_id字段,经过这个字段进行关联。
但这种实体关系为1:1,强关联的关系
推荐设计成以下模式:
{
_id: "joe",
name: "Joe Bookreader",
address: {
street: "123 Fake Street",
city: "Faketon",
state: "MA",
zip: "12345"
}
}
即便用内嵌模式,将数据存储在一个集合中。
一个顾客维护一个地址是理想的情况,回头看看咱们淘宝帐号,就会发现收货地址通常都是2个以上 ( 流泪 ╥╯^╰╥)
patron 集合顾客joe的文档记录
{
_id: "joe",
name: "Joe Bookreader"
}
address 集合joe顾客的地址1的文档记录
{
patron_id: "joe",
street: "123 Fake Street",
city: "Faketon",
state: "MA",
zip: "12345"
}
address 集合中joe顾客的地址2的文档记录
{
patron_id: "joe",
street: "1 Some Other Street",
city: "Boston",
state: "MA",
zip: "12345"
}
像这种1:N的关系,而且N能够预见不是不少的状况下,咱们推荐采用内嵌模式,
将集合文档设计成以下模式:
{
_id: "joe",
name: "Joe Bookreader",
addresses: [
{
street: "123 Fake Street",
city: "Faketon",
state: "MA",
zip: "12345"
},
{
street: "1 Some Other Street",
city: "Boston",
state: "MA",
zip: "12345"
}
]
}
与案例1的不一样就是地址信息采用了数组类型,数组的字段值又为内嵌子文档。
上面介绍的是1对多的关系(1:N),可是N值不是很大。可是现实世界中,有时候会遇到N值比较大的状况。
好比 出版社和书籍的关系,一个出版社可能已将出版了成千上万本书籍了。
其设计模式能够以下(内嵌模式),将出版社的信息做为一个子文档,来内嵌到书籍的文档中,具体信息以下:
如下书籍《MongoDB: The Definitive Guide》的文档信息:
{
title: "MongoDB: The Definitive Guide",
author: [ "Kristina Chodorow", "Mike Dirolf" ],
published_date: ISODate("2010-09-24"),
pages: 216,
language: "English",
publisher: {
name: "O'Reilly Media",
founded: 1980,
location: "CA"
}
}
如下书籍《50 Tips and Tricks for MongoDB Developer》的文档信息:
{
title: "50 Tips and Tricks for MongoDB Developer",
author: "Kristina Chodorow",
published_date: ISODate("2011-05-06"),
pages: 68,
language: "English",
publisher: {
name: "O'Reilly Media",
founded: 1980,
location: "CA"
}
}
从中能够看出,publisher信息描述比较多,而且都相同,每一个文档中都存放,浪费太多的存储空间,显得无用臃肿,还有个明显的缺点就是 当publisher数据更新时,须要对全部的书籍文档进行刷新。理所固然地,就会想到将出版社独立出来,单独设计一个文档。(引用模式)。
咱们能够这样设计:出版社单独设计为一个集合文档(文档中引用书籍的编号),以下:
{
name: "O'Reilly Media",
founded: 1980,
location: "CA",
books: [123456789, 234567890, ...]
}
书籍集合中编号为123456789的书籍的文档:
{
_id: 123456789,
title: "MongoDB: The Definitive Guide",
author: [ "Kristina Chodorow", "Mike Dirolf" ],
published_date: ISODate("2010-09-24"),
pages: 216,
language: "English"
}
书籍集合中编号为234567890的书籍的文档:
{
_id: 234567890,
title: "50 Tips and Tricks for MongoDB Developer",
author: "Kristina Chodorow",
published_date: ISODate("2011-05-06"),
pages: 68,
language: "English"
}
此设计中,将出版社出版的书的编号,保存在了出版社这个集合中。
可是这种设计仍是有问题,例如,数组的更新、删除相对比较困难。还有就是,每增长一个书籍集合的文档,同时还要修改这个出版社结合的文档。 因此,咱们还能够将这种集合文档设计优化以下。
此时出版社的文档记录以下:(再也不应用书籍文档的编号)
{
_id: "oreilly",
name: "O'Reilly Media",
founded: 1980,
location: "CA"
}
此时书籍的文档记录以下:(书籍为123456789,文档引用了出版社的_ID)
{
_id: 123456789,
title: "MongoDB: The Definitive Guide",
author: [ "Kristina Chodorow", "Mike Dirolf" ],
published_date: ISODate("2010-09-24"),
pages: 216,
language: "English",
publisher_id: "oreilly"
}
此时书籍的文档记录以下:(书籍为234567890,文档引用了出版社的_ID)
{
_id: 234567890,
title: "50 Tips and Tricks for MongoDB Developer",
author: "Kristina Chodorow",
published_date: ISODate("2011-05-06"),
pages: 68,
language: "English",
publisher_id: "oreilly"
}
上面三个例子,在关系型数据库中均可以用咱们学习过的关系(例如1:1;1:N)来描述,那么咱们再举一个关系型数据库难以描述的关系 -- 树状关系。
例如,咱们在电商网站上常见的商品分类关系,一级商品、二级商品、三级商品、四级商品关系。咱们简化此例子以下:
那么在MongoDB中能够轻松实现他们关系的查询。
文档的设计为:
db.categories.insert( { _id: "MongoDB", parent: "Databases" } )
db.categories.insert( { _id: "dbm", parent: "Databases" } )
db.categories.insert( { _id: "Databases", parent: "Programming" } )
db.categories.insert( { _id: "Languages", parent: "Programming" } )
db.categories.insert( { _id: "Programming", parent: "Books" } )
db.categories.insert( { _id: "Books", parent: null } )
查询节点的父节点(或称为查询上一级分类)的语句,例如查询MongoDB所属分类:
db.categories.findOne( { _id: "MongoDB" } ).parent
查询节点的子节点(或者为查询下一级分类),例如查询Database的直连的子节点(不是孙子节点)。
db.categories.find( { parent: "Databases" } )
上面的文档能够查询出子文档,可是会显示出多个文档,例如上面的查询语句,会返回出MongoDB 文档和 dbm文档 ,咱们还须要还特殊处理,那么可不能够在一个文档中显示出因此的子节点呢?
能够的。文档模式设计以下:
db.categories.insert( { _id: "MongoDB", children: [] } )
db.categories.insert( { _id: "dbm", children: [] } )
db.categories.insert( { _id: "Databases", children: [ "MongoDB", "dbm" ] } )
db.categories.insert( { _id: "Languages", children: [] } )
db.categories.insert( { _id: "Programming", children: [ "Databases", "Languages" ] } )
db.categories.insert( { _id: "Books", children: [ "Programming" ] } )
若是这时候查询Databases的子节点,就会是一个文档了。查询验证语句以下:
db.categories.findOne( { _id: "Databases" } ).children
此模式也支持查询节点的父节点。例如查询MongoDB这个节点的父节点:
db.categories.find( { children: "MongoDB" } )
其文档设计为:
db.categories.insert( { _id: "MongoDB", ancestors: [ "Books", "Programming", "Databases" ], parent: "Databases" } )
db.categories.insert( { _id: "dbm", ancestors: [ "Books", "Programming", "Databases" ], parent: "Databases" } )
db.categories.insert( { _id: "Databases", ancestors: [ "Books", "Programming" ], parent: "Programming" } )
db.categories.insert( { _id: "Languages", ancestors: [ "Books", "Programming" ], parent: "Programming" } )
db.categories.insert( { _id: "Programming", ancestors: [ "Books" ], parent: "Books" } )
db.categories.insert( { _id: "Books", ancestors: [ ], parent: null } )
例如查询MongoDB节点的祖先节点:
db.categories.findOne( { _id: "MongoDB" } ).ancestors
固然也能够查询 后代节点:
db.categories.find( { ancestors: "Programming" } )
MongoDB的模式设计是一个比较大的课题,须要多看看情景案例,多品味一些优秀的文档设计,多问些问什么要这样作,是否有更优的设计,要慢慢去领悟MongoDB的哲学思想。
总之,这是一个多看、多想、多思的蜕变羽化过程,可能时间很长、过程有些痛苦。
本文版权归做者全部,未经做者赞成不得转载,谢谢配合!!!
本文版权归做者全部,未经做者赞成不得转载,谢谢配合!!!