关系型数据库的价值

获取持久化数据

  • 持久储存大量数据
  • 在大多数的计算机架构中,有两个存储区域
    • 速度快但数据易丢失的“主存储器”
      • 空间有限
      • 易挥发
    • 存储量大但速度较慢的“后备存储器”
      • 文件系统
      • 数据库

并发

  • 多个用户会一起访问同一份数据体,并且可能要修改这份数据。(大多数情况下,他们都在不同数据区域内各自操作,但是,偶尔也会同时操作一小块数据)

  • 关系型数据库提供了 “事务”机制来控制对其数据的访问,以便处理此问题。

  • 事务在处理错误时也有用。通过事务更改数据时,如果在处理变更的过程中出错了,那么就可以回滚(roll back)这一事务,以保证数据不受破坏。

集成

  • 企业级应用程序居于一个丰富的生态系统中,它需要与其他应用程序协同工作。不同的应用程序经常要使用同一份数据,而且某个应用程序更新完数据之后,必须让其他应用程序知道这份数据已经改变了。

  • 常用的办法是使用共享数据库集成(shared database integration) ,多个应用程序都将数据保存在同一个数据库中。这样一来,所有应用程序很容易就能使用彼此的数据了。

  • 与多用户访问单一应用程序时一样,数据库的并发控制机制也可以应对多个应用程序。

近乎标准的模型

  • 关系型数据库以近乎标准的方式提供了数据模型。

  • 尽管各种关系型数据库之间仍有差异,但其核心机制相同

    • 不同厂商的SQL方言相似
    • “事务” 的操作方式也几乎一样

由来

阻抗失谐

  • 基于关系代数(relational algebra),关系模型把数据组织成 “关系”(relation)和“元组”(tuple)。

    • 元组是由“键值对”(name-value pair)构成的集合
    • 而关系则是元组的集合。
    • SQL操作所使用及返回的数据都是“关系”
    • 元组不能包含“嵌套记录”(nested record)或“列表”(list) 等任何结构
  • 而内存中的数据结构则无此限制,它可以使用的数据组织形式比“关系”更丰富。

  • 关系模型和内存中的数据结构之间存在差异。这种现象通常称为“阻抗失谐”。

    • 如果在内存中使用了较为丰富的数据结构,那么要把它保存到磁盘之前,必须先将其转换成“关系形式。于是就发生了“阻抗失谐”:需要在两种不同的表示形式之间转译

image-20220510152801602

解决办法

  • 面向对象数据库
  • 对象-关系映射框架:通过映射模式表达转换

存在查询性能问题和集成问题

集成数据库

image-20220510153828335

应用程序数据库

image-20220510153857086

集群问题

image-20220510154614961

$NoSQL$

image-20220510154643935

聚合

把一组相互关联的对象视为一个整体单元来操作,而这个单元就叫聚合(aggregate)。

  • 通过原子操作(atomic operation)更新聚合的值(含一致性管理)

  • 以聚合为单位与数据存储通信

  • 在集群中操作数据库时,用聚合为单位来复制和分片

  • 由于程序员经常通过聚合结构来操作数据,故而采用聚合也能让其工作更为轻松。面向聚合操作数据时所用的单元,其结构比元组集合复杂得多

  • “键值数据库”、“文档数据库”、“列族数据库”

image-20220513144105476

image-20220513144115333

image-20220513144127050

image-20220513144152085

聚合无知

关系型数据库的数据模型中,没有“聚合”这一概念,因此我们称之为“聚合无知”(aggregate- ignorant)。

  • “图数据库”也是聚合无知的。

聚合反应数据操作的边界,很难在共享数据的多个场景中“正确” 划分,对某些数据交互有用的聚合结构,可能会阻碍另一些数据交互

  • 在客户下单并核查订单,以及零售商处理订单时,将订单视为一个聚合结构就比较合适。

  • 如零售商要分析过去几个月的产品销售情况,那么把订单做成一个聚合结构反而麻烦了。要取得商品销售记录,就必须深挖数据库中的每一个聚合。

  • 若是采用“聚合无知模型”,那么很容易就能以不同方式来查看数据

在操作数据时,如果没有一种占主导地位的结构,那么选用此模型效果会更好。

聚合之间的关系

例如:把订单和客户放在两个聚合中,但是想在它们之间设定某种关系,以便能根据订单查出客户数据。

  • 要提供这种关联,最简单的办法就是把客户ID嵌入订单的聚合数据中。在应用层级提供关联。

  • 在数据库层级提供聚合之间关系的表达机制

操作多个有关联的聚合,由应用保证其正确性

  • 面向聚合数据库获取数据时以聚合为单元,只能保证单一聚合内部内容的原子性。

聚合、集群和事务处理

在集群上运行时,需要把采集数据时所需的节点数降至最小

  • 如果在数据库中明确包含聚合结构,那么它就可以根据这一重要信息,知道哪些数据需要一起操作了,而且这些数据应该放在同一个节点中

通常情况下,面向聚合的数据库不支持跨越多个聚合的ACID事务。它每次只能在一个聚合结构上执行原子操作。

  • 如果想以原子方式操作多个聚合,那么就必须自己组织应用程序的代码

  • 在实际应用中,大多数原子操作都可以局限于某个聚合结构内部,而且,在将数据划分为聚合时,这也是要考虑的因素之一

主要的$NoSQL$数据模型

键值数据模型与文档数据模型

这两类数据库都包含大量聚合,每个聚合中都有一个获取数据所用的键或ID。

两种模型的区别是:

  • 键值数据库的聚合不透明,只包含一些没有太多意义的大块信息
    • 聚合中可以存储任意数据。数据库可能会限制聚合的总大小,但除此之外,其他方面都很随意
    • 在键值数据库中,要访问聚合内容,只能通过键来查找
  • 在文档数据库的聚合中,可以看到其结构。
    • 限制其中存放的内容,它定义了其允许的结构与数据类型
    • 能够更加灵活地访问数据。通过用聚合中的字段查询,可以只获取一部分聚合,而不用获取全部内容
    • 可以按照聚合内容创建索引

列族存储

大部分数据库都以行为单元存储数据。然而,有些情况下写入操作执行得很少,但是经常需要一次读取若干行中的很多列。此时,列存储数据库将所有行的某一组作为基本存储单元

列族数据库将列组织为列族。每一列都必须是某个列族的一部分,而且访问数据的单元也得是列

  • 某个列族中的数据经常需要一起访问

列族模型将其视为两级聚合结构

  • 与“键值存储”相同,第一个键通常代表行标识符,可以用它来获取想要的聚合
  • 列族结构与“键值存储”的区别在于,其“行聚合”本身又是一个映射,其中包含一些更为详细的值。这些“二级值”就叫做“列”。与整体访问某行数据一样,我们可以操作特定的列

image-20220513150121818

两种数据组织方式

  • 面向行:每一行都是一个聚合,例如ID为1234的顾客就是一个聚合,该聚合内部存有一些包含有用数据块(客户信息、订单纪录)的列族
  • 面向列:每个列族都定义了一种记录类型(例如客户信息),其中每行都表示一条记录。数据库中的大“行”理解为列族中每一个短行记录的串接

面向聚合的数据模型

共同点

  • 都使用聚合这一概念,而且聚合中都有一个可以查找其内容的索引键。

  • 在集群上运行时,聚合是中心环节,因为数据库必须保证将聚合内的数据存放在同一个节点上。

  • 聚合是“更新”操作的最小数据单位(atomic unit),对事务控制来说,以聚合为操作单元

差别

  • 键值数据模型将聚合看作不透明的整体,只能根据键来查出整个聚合,而不能仅仅查询或获取其中的一部分

  • 文档模型的聚合对数据库透明,于是就可以只查询并获取其中一部分数据了,不过,由于文档没有模式,因此在想优化存储并获取聚合中的部分内容时,数据库不太好调整文档结构

  • 列族模型把聚合分为列族,让数据库将其视为行聚合内的一个数据单元。此类聚合的结构有某种限制,但是数据库可利用此种结构的优点来提高其易访问性。

图数据库

image-20220513150946662

  • 图数据库的基本数据模型:由边(或称“弧”,arc)连接而成的若干节点。

  • 可以用专门为“图”而设计的查询操作来搜寻图数据库的网络了

    • 指定节点,通过边进行查询
  • 关系型数据可以通过“外键”实现,查询中的多次连接,效率较差

无模式

$NoSQL$数据库无模式:

  • “键值数据库”可以把任何数据存放在一个“键”的名下
  • “文档数据库”对所存储的文档结构没有限制
  • 在列族数据库中,任意列里面都可以随意存放数据
  • 图数据库中可以新增边,也可以随意向节点和边中添加属性

格式不一致的数据

每条记录都拥有不同字段集(set of field)

关系型数据库中,“模式”会将表内每一行的数据类型强行统一,若不同行所存放的数据类型不同,那这么做就很别扭。

  • 要么得分别用很多列来存放这些数据,而且把用不到的字段值填成null(这就成了”稀疏表”,sparse table),

  • 要么就要使用类似custom column 4这样没有意义的列类型。

无模式表则没有这么麻烦,每条记录只要包含其需要的数据即可,不用再担心上面的问题了。

无模式的问题

存在“隐含模式”。在编写数据操作代码时,对数据结构所做的一系列假设

  • 应用与数据的耦合问题

  • 无法在数据库层级优化和验证数据

在集成数据库中,很难解决

  • 使用应用程序数据库,并使用$Web Services、SOA$等完成集成

  • 在聚合中为不同应用程序明确划分出不同区域

    • 在文档数据库中,可以把文档分成不同的区段(section)
    • 在列族数据库,可以把不同的列族分给不同的应用程序

分布式数据库

数据分布有两条路径,既可以在两者中选一个来用,也可以同时使用它们

  • 分片:将不同数据存放在不同节点中
  • 复制:将同一份数据拷贝至多个节点
    • 主从式和对等式

分片

一般来说,数据库的繁忙体现在:不同用户需要访问数据集中的不同部分,在这种情况下,把数据的各个部分存放于不同的服务器中,一次实现横向扩展,这种技术就叫分片

image-20220517142149738

在理想情况下,不同的服务器节点会服务于不同的用户。每位用户只需与一台服务器通信,并且很快就能获得服务器的响应。网络负载相当均衡地分布于各台服务器上。

为达成目标,必须保证需要同时访问的那些数据都存放在同一节点上,而且节点必须排布好这些数据块,使访问速度最优。

  • 若使用面向聚合的数据库,可以把聚合作为分布数据的单元。

  • 在节点的数据排布问题上,有若干个与性能改善相关的因素。

    • 地理因素
    • 负载均衡
    • 聚合有序放置

采用应用程序的逻辑实现分片

  • 编程模型复杂化,因为应用程序的代码必须负责把查询操作分布到多个分片上

  • 若想重新调整分片,那么既要修改程序代码,又要迁移数据

采用NoSQL数据库提供的“自动分片”( auto-sharding)功能

  • 让数据库自己负责把数据分布到各分片

  • 并且将数据访问请求引导至适当的分片上

分片可以同时提升读取与写入效率

  • 使用“复制”技术,尤其是带缓存的复制,可以极大地改善读取性能,但对于写操作帮助不大

分片对改善数据库的“故障恢复能力”帮助并不大。尽管数据分布在不同的节点上,但是和“单一服务器”方案一样,只要某节点出错,那么该分片上的数据就无法访问了

  • 在发生故障时,只有访问此数据的那些用户才会受影响,而其余用户则能正常访问

  • 由于多节点问题,从实际效果出发,分片技术可能会降低数据库的错误恢复能力

复制

image-20220517143745975

image-20220517145103719

image-20220517145130732

image-20220517145150224

image-20220517145208907

分布式模型中的一致性

当两个客户端试图同时修改一份数据时,会发生“写入冲突”。而当某客户端在另一客户端执行写入操作的过程中读取数据时,则会发生“读写冲突”

悲观方式以锁定数据纪律来避免冲突:写入锁

乐观方式则在事后检测冲突并将其恢复

  • “条件更新”,任意客户在执行更新操作之前,都要先测试数据的当前值和上一次读入的值是否相同
  • 保存冲突数据。用户自行合并或自动合并

“图数据库”常常和关系型数据库一样,也支持ACID事务

面向聚合的数据库通常支持“原子更新”,但仅限于单一聚合内部,“一致性”可以在某个聚合内部保持,但在各聚合之间则不行,在执行影响多个聚合的更新操作时,会留下一段时间空挡,让客户端有可能在此刻读出逻辑不一致的数据,存在不一致风险的时间长度就叫“不一致窗口”。

复制一致性:

要求从不同副本中欧冠读取同一个数据项时,所得到的值相同

image-20220524142645711

  • 在分布式系统中,如果某些节点收到了更新数据,而另外一些节点却尚未收到,那么这种情况就视为“读写冲突”。

  • 若写入操作已经传播至所有节点,则此刻的数据库就具备“最终一致性”( eventually consistent)

  • 复制不一致性带来的“不一致窗口”,在考虑网络环境后,会比单一节点导致的“不一致窗口”长的多

    • 不一致性窗口对应用的影响不同

image-20220524143251097

image-20220524143304214