MongoDB模式设计

信用

模式设计

在关系数据库中设计数据库是一种理想的方法-第三种范式。 在MongoDB ,将数据保存在有利于使用数据的应用程序的位置上很重要。 你在想

  • 应用程序数据模式
  • 哪些数据一起使用
  • 哪些数据片段主要是只读的
  • 一直写什么数据

相反,在关系型DBMS中,数据的组织方式与应用程序无关。

MongoDB支持丰富的文档 。 我们可以存储一个项目数组,某个键的值可以是整个其他文档。 这将使我们能够预连接/嵌入数据以进行快速访问。 这很重要,因为MongoDB 不支持直接在内核内部进行联接 。 相反,如果需要加入,则需要加入应用程序本身。 加入的原因很难扩展 。 这迫使我们提前考虑要与其他数据一起使用的数据 。 我们可能希望将数据直接嵌入文档中。 没有限制 。 对于MongoDB ,由于嵌入,它与我们认为的一样重要。 MongoDB以某种方式考虑原子性 。 同样,它不支持事务 ,但是在一个文档中支持原子操作 。 数据需要以支持原子操作的方式进行设计。

没有声明的架构,但是应用程序很有可能会具有架构。 通过具有模式,我们的意思是特定集合中的每个文档都可能具有非常相似的结构。 根据您应用程序的不同版本,该结构可能会有一些小的更改。 即使未提前声明,也必须考虑数据结构,以便数据模式本身支持应用程序的所有不同功能,这一点很重要

关系归一化

让我们看一下下面的博客文章项目非规范化表。 它不是第三范式,已损坏。 假设有多个作者相同的帖子,我们可能会更新几行,而其他行未更新。 保持表数据不一致。

因此,这违反了规范化,因为它违反了以第三范式描述规范化表的通用方法,即表中的每个非键属性都必须提供有关键,整个键以及除键之外的任何事实 。 这就是说出你在美国法庭上所说的话的故事,说出真相,全部真相,只有真相。 在这种情况下,关键字是“ Post Id并且有一个非关键字属性“ Author Email ”没有遵循该属性。 因为确实如此,实际上可以告诉作者一些信息。 因此,它违反了该第三范式。

上表可以在MongoDB表示为:

{
id: 'some id',
title: 'some title',
body: 'some content here',
author: {
name: 'author name',
email: 'author email id'
}
}

刷新-标准化的目标是什么?

  • 将数据库从修改异常中解放出来 -对于MongoDB ,似乎嵌入数据通常会导致这种情况。 实际上,我们应该避免在MongoDB中的文档中嵌入数据,这可能会导致这些异常。 有时,出于性能原因,我们可能需要在文档中重复数据。 但是,这不是默认方法。 默认是避免它。
  • 扩展时应尽量减少重新设计 MongoDB足够灵活,因为它允许添加密钥而无需重新设计所有文档
  • 避免偏向任何特定的访问模式 -这是一件事,在MongoDB描述架构时,我们无需担心。 MongoDB背后的想法之一是将数据库调整为我们要编写的应用程序以及我们要解决的问题。

关系数据库的一大优点是,它真正可以使数据库中的数据保持一致。 它执行此操作的方法之一是使用外键。 外键约束是,假设有一个带有某些列的表,该表将具有一个外键列,该列具有来自另一个表的列的值。 在MongoDB ,不能保证将保留外键。 程序员应确保以这种方式确保数据的一致性。 在将来MongoDB版本中,这可能是可行的,但是今天,没有这样的选择。 外键约束的替代方法是嵌入数据

没有交易生活

事务支持ACID属性,但是尽管MongoDB中没有事务,但我们确实有原子操作。 好吧,原子操作意味着当您处理单个文档时,该工作将在其他人看到该文档之前完成。 他们将看到我们所做的所有更改,或者全部都看不到。 使用原子操作,您通常可以完成与使用关系数据库中的事务可以完成的相同的工作。 原因是,在关系数据库中,我们需要跨多个表进行更改。 通常,表需要连接,因此我们希望一次完成所有连接。 要做到这一点,由于有多个表,我们必须开始一个事务并进行所有这些更新,然后结束该事务。 但是,使用MongoDB ,我们将嵌入数据,因为我们将其预先加入文档中,而这些正是具有层次结构的丰富文档。 我们通常可以完成同一件事。 例如,在博客示例中,如果我们要确保原子更新博客文章,则可以这样做,因为我们可以立即更新整个博客文章。 好像是一堆关系表一样,我们可能必须打开一个事务,以便我们可以更新post集合和comment集合。

那么,在MongoDB可以采取哪些方法来克服交易不足?

  • 重组 —重组代码,以便我们在单个文档中进行工作并利用我们在该文档中提供的原子操作。 如果我们这样做,通常情况下我们都准备就绪。
  • 在软件中实现 -通过创建关键部分,我们可以在软件中实现锁定。 我们可以使用查找和修改来构建测试,测试和设置。 如果需要,我们可以建立信号灯。 从某种意义上讲,这就是大世界的运作方式。 如果我们考虑一下,如果一家银行需要将资金转移到另一家银行,那么它们就不在同一个关系系统中。 他们每个人经常都有自己的关系数据库。 即使我们不能跨数据库系统开始事务和结束事务,也只能在一家银行的一个系统中,他们必须能够协调该操作。 因此,软件中肯定有解决该问题的方法。
  • 容忍 -最终方法通常是为了容忍一点不一致,它通常可以在现代Web应用程序和需要大量数据的其他应用程序中使用,这是最终的方法。 例如,如果我们谈论的是Facebook中的朋友供稿,那么每个人是否同时看到您的墙更新都无关紧要。 如果很好,那么如果一个人落后几秒钟,他们就追上了。 在许多系统设计中,通常并不重要的一点是,所有内容都应保持完全一致,并且每个人都具有完全一致的数据库视图。 因此,我们可以简单地容忍一些暂时性的不一致。

UpdatefindAndModify$addToSet (在更新内)和$push (在更新内)操作在单个文档中原子地操作。

一对一关系

一对一关系是每个项目恰好对应另一个项目的关系。 例如:

  • 员工有简历,反之亦然
  • 建筑物有平面图,反之亦然
  • 患者有病史,反之亦然
//employee
{
_id : '25',
name: 'john doe',
resume: 30
}
//resume
{
_id : '30',
jobs: [....],
education: [...],
employee: 25
}

我们可以通过具有员工集合和简历集合,并使员工通过链接指向简历来建立员工-简历关系的模型,其中我们具有一个与简历集合中的ID对应的ID 。 或者,如果愿意,我们可以朝另一个方向链接,在简历集合中有一个员工密钥,它可能指向员工本身。 或者,如果需要,我们可以嵌入。 因此,我们可以拿走整个简历文档,然后将其嵌入到员工集合中,反之亦然。

该嵌入取决于应用程序如何访问数据以及数据访问的频率。 我们需要考虑:

  • 访问频率
  • 项目的大小-一直在增长的事物以及没有增长的事物。 因此,每次我们向文档中添加某些内容时,都有一点需要在集合中移动文档。 如果文档大小超过16 MB ,这几乎是不可能的。
  • 数据的原子性– MongoDB没有事务,对单个文档有原子操作。 因此,如果我们知道我们不能承受任何不一致的情况,并且希望能够一直更新整个员工以及简历,那么我们可以决定将它们放入同一文档中,然后以一种或另一种方式嵌入它们。我们可以立即更新所有内容。

一对多关系

在这种关系中,有许多实体或许多实体映射到一个实体。 例如:

  • 一个城市有很多人住在那个城市。 假设纽约有800万人。

让我们假设下面的数据模型:

//city
{
_id: 1,
name: 'NYC',
area: 30,
people: [{
_id: 1,
name: 'name',
gender: 'gender'
.....
},
....
8 million people data inside this array
....
]
}

这将不起作用,因为那将是非常巨大的。 让我们尝试翻转头部。

//people
{
_id: 1,
name: 'John Doe',
gender: gender,
city: {
_id: 1,
name: 'NYC',
area: '30'
.....
}
}

现在,这种设计的问题在于,如果显然有多个人居住在纽约市,那么我们对城市数据做了很多重复。

建模此数据的最佳方法可能是使用true链接

//people
{
_id: 1,
name: 'John Doe',
gender: gender,
city: 'NYC'
}
//city
{
_id: 'NYC',
...
}

在这种情况下, people集合可以链接到city集合。 知道我们没有外键约束,因此我们必须保持一致。 因此,这是一对多的关系。 它需要2个集合。 对于一对一(也就是一对多)的小关系,诸如博客文章发表评论之类的关系。 注释可以作为数组嵌入到帖子文档中。

因此,如果确实是一对多,那么2个集合最适合链接。 但是对于一个到几个,单个集合通常就足够了。

多对多关系

例如:

  • 给作者的书
  • 学生对老师

发给作者的书是几对几的关系,因此我们可以在另一人的文档中包含一系列书或作者。 学生和老师也一样。 我们还可能存在重复的风险。 但是,这将要求每个学生在插入之前在系统中都有一位老师,反之亦然。 应用程序逻辑可能始终不允许它。 换句话说,父对象必须存在,子对象才能存在。

多键

多键索引是该功能,因为它的链接和嵌入效果很好。 假设我们有两个针对学生和老师的模式。

//students
{
_id: 1,
name: 'John Doe',
teachers: [1,7,10,23]
}
//teachers
{
_id: '10',
name:'Tony Stark'
}

现在有两个明显的查询。

  • 如何找到特定学生拥有的所有老师? -可以通过在学生集合中寻找老师的关键来搜索
  • 如何找到所有有特定老师的学生? —这是一个有点困难的查询,需要使用set运算符。 为了提高效率,我们需要使用Multikey索引

要在students集合的teachers列上创建索引,请使用db.students.ensureIndex({ teachers : 1 })

现在查找所有曾经有特定老师的学生吗? 使用查询db.students.find( { 'teachers': {$all: [0,1]}} ) 。 现在,如果我们将.explain()附加到上述查询中-它通过显示在哪里应用键等来显示内部工作。

嵌入的好处

MongoDB嵌入文档的主要原因是性能。 主要的性能优势来自提高的读取性能。 现在,为什么我们要获得读取性能。 原因是计算机系统的构建方式的本质,即它们通常具有旋转磁盘,而这些旋转磁盘具有很高的延迟 ,这意味着它们要花很长时间(最多1ms)才能到达第一个字节。 但是,当访问第一个字节时,每个其他字节很快就会到来。 因此它们往往是相当高的带宽 。 因此,我们的想法是,如果我们可以将要一起使用的数据放置在同一文档中,将其嵌入,然后我们将旋转磁盘,找到需要此信息的扇区,然后我们将开始阅读它。 我们将一次性获得所需的所有信息。 这也意味着如果我们有2条数据,这些数据通常位于2个集合或几个关系数据库表中。 相反,它们在一个文档中,因此避免了往返数据库的麻烦

树木

模式设计领域的一个经典问题是如何在数据库中表示一棵树? 让我们看一下在电子商务网站(例如Amazon)中表示电子商务类别的示例问题。 我们在家里,户外,冬天,下雪的地方。 想法是我们有products 。 此外我们还有一category ,在这里我们可以查找7类别 ,看到了categoryName ,有的为属性的category

//products
{
category: 7,
productName: 'ABC'
}
//category
{
_id: '7',
categoryName:'outdoors',
parent: 6
}

一种方法是保留parent ID,这可能是我们可以在简单的关系数据库中执行的操作。 但这并不容易找到该category所有parent 。 我们必须迭代查询,找到它的父代和父代,直到我们到达顶部为止。

因此,在MongoDB执行此操作的另一种方法是能够列出祖先或孩子。 因此,让我们考虑一下这以及它如何工作。 因此,我们可以决定列出此category所有子category

//category
{
_id: '7',
categoryName:'outdoors',
children: [3,6,7,9]
}

如果我们希望能够查找并找到某棵树上方的整个子树,那也相当有限。 取而代之的是,通过将数组放入MongoDB内的能力一次又一次地运作良好,是从顶部开始依次列出祖先。

//category
{
_id: '7',
categoryName:'outdoors',
ancestors: [3,7,5,8,9]
}

同样,结构化和表达丰富数据的能力也是使MongoDB如此有趣的原因之一。 在关系数据库中很难做到这一点。 现在,就如何表示product category层次结构之category的数据而言,这再次取决于访问模式。 这取决于我们认为我们将需要显示数据,为用户访问数据的方式。 然后基于此,我们知道如何对其进行建模。

何时非正规化

关系数据库中进行规范化的原因之一是避免数据重复带来的修改异常。 而且,当我们查看MongoDB及其结构如何允许这些丰富的文档时,很容易假设我们正在做的是对数据进行非规范化。 在一定程度上,这是对的。 只要我们不重复数据,就不会为修改异常打开自己的大门。

通常,在一对一关系的情况下嵌入数据是很好的。 在一对多关系的情况下,只要我们从多对一关系进行嵌入,嵌入也可以很好地工作而无需重复数据。 现在,如果我们从一个到多个,那么链接将避免重复数据。 现在,如果我们要嵌入某些内容,即使出于性能原因它导致数据重复也要与应用程序的访问模式匹配。 这可能是有道理的,尤其是在数据很少更改或更新的情况下。 但是,即使我们之间从多到多,我们也可以经常避免这种情况。 在我们与studentsteachersauthorsbooks探讨的多对多关系中,如果要避免非规范化带来的修改异常,我们要做的就是通过文档中object ids的数组进行链接。

这些都是准则。 对于现实世界的应用程序,出于性能原因,我们可能需要嵌入数据,以匹配数据访问模式-可能需要嵌入数据。

相片

最初发布于 xameeramir.github.io

翻译自: https://hackernoon.com/mongodb-schema-design-86327d8fae83

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值