第九章 关系查询处理和查询优化

查询处理

image-20220503140934704

查询分析

  • 对查询语句进行扫描、词法分析和语法分析

查询检查

  • 合法权检查
  • 视图转换
  • 安全性检查
  • 完整性初步检查

根据数据字典中有关的模式定义检查语句中的数据库对象,如关系名、属性名是否存在和有效

如果是对视图的操作,则要用视图消解方法把对视图的操作转换成对基本表的操作

根据数据字典中的用户权限和完整性约束定义对用户的存取权限进行检查

检查通过后把SQL查询语句转换成内部表示,即等价的关系代数表达式。

关系数据库管理系统一般都用查询树,也称为语法分析树来表示扩展的关系代数表达式。

查询优化

  • 代数优化/逻辑优化:指关系代数表达式的优化
  • 物理优化:指存取路径和低层操作算法的选择

选择依据:

  • 基于规则
  • 基于代价
  • 基于语义

查询执行

依据优化器得到的执行策略生成查询执行计划

代码生成器生成执行查询计划的代码

两种执行方法:

  • 自顶向下
  • 自底向上

实现查询操作

选择操作典型实现方法:

  • 全表扫描方法
    • 对查询的基本表顺序扫描,逐一检查每个元组是否满足选择条件,把满足条件的元组作为结果输出
    • 适合小表不适合大表
  • 索引扫描方法
    • 适合于选择条件中的属性上有索引(例如B+树索引或Hash索引)
    • 通过索引先找到满足条件的元组主码或元组指针,再通过元组指针直接在查询的基本表中找到元组

image-20220503144756890

image-20220503144808809

image-20220503144832554

image-20220503145007970

image-20220503145146857

连接操作的实现:

连接操作是查询处理中最耗时的操作之一

  • 嵌套循环算法

image-20220503145416761

  • 排序-合并算法

image-20220503145454434

image-20220503145654638

  • 索引连接算法

image-20220503145759318

  • Hash Join算法

image-20220503145935530

查询优化

关系系统的查询优化是关系数据库管理系统实现的关键技术优势关系系统的有点所在,减轻了用户选择存取路径的负担

关系查询优化是影响关系数据库管理系统性能的关键因素

由于关系表达式的语义级别很高,使关系系统可以从关系表达式中分析查询语义,提供了执行查询优化的可能性

查询优化的优点:

image-20220503151418874

查询优化的总目标:

image-20220503151523782

有选择和连接操作时,先做选择操作,这样连接的元组可以大大减少,这就是代数优化

采用索引可以减少连接时间,这就是物理优化

代数优化

  • 策略:才有等价变换来提高查询效率
  • 关系代数表达式的等价:指用相同的关系代替两个表达式中相应的关系所得到的结果是相同的
  • 两个关系表达式E1和E2是等价的,可记为E1≡E2

常用的等价变换规则:

image-20220503153407802

image-20220503153628430

image-20220503153853680

image-20220503153919467

启发式规则

image-20220503154002111

查询树的启发式优化

image-20220510141358799

image-20220510141546508

image-20220510142142670

一个例字

image-20220510142201077

image-20220510142330532

image-20220510142407610

image-20220510142439888

image-20220510142510083

image-20220510142614737

image-20220510142626654

image-20220510142639078

image-20220510142651981

物理优化

代数优化改变查询语句中操作的次序和组合,不涉及底层的存取路径,对于一个查询语句有许多存取方案,它们的执行效率不同, 仅仅进行代数优化是不够的 ,物理优化就是要选择高效合理的操作算法或存取路径,求得优化的查询计划

优化方法:

  • 基于规则的启发式优化(遇到A做$A_1$,遇到B做$B_1$)
    • 启发式规则是指那些在大多数情况下都适用,但不是在每种情况下都是最好的规则
  • 基于代价估算的优化
    • 优化器估算不同执行策略的代价,并选出具有最小代价的执行计划
  • 两者结合的优化方法
    • 常常先使用启发式规则,选取若干较优的候选方案,减少代价估算的工作量,然后分别计算这些候选方案的执行代价,较快地选出最终的优化方案

启发式规则

选择操作

image-20220510144116595

连接操作

image-20220510145251581

基于代价的优化

  • 启发式规则优化是定性的选择,适合解释执行的系统

    • 解释执行的系统,优化开销包含在查询总开销之中
  • 编译执行的系统中查询优化和查询执行是分开的

    • 可以采用精细复杂一些的基于代价的优化方法

image-20220510145855060

image-20220510145938355

image-20220510145951615

第十章 数据库恢复技术

事务的基本概念

事务

  • 用户定义的一个数据库操作序列,这些操作要么全做,要么全不做,是一个不可分个的工作单位

  • 一个事务可以是一条SQL语句,一组SQL语句或整个程序。一般来讲一个程序中包含多个事务

  • 事务是恢复和并发控制的基本单位

定义事务一般有三条:

1
2
3
BEGIN TRANSACTION;
COMMIT;
ROLLBACK;

事务通常以BEGIN TRANSACTION开始。COMMIT表示提交,ROLLBACK表示回滚,即在运行过程中发生某种故障,事务不能继续执行,系统将事务中对数据库的所有已完成的操作全部撤销,回滚到事务开始时的状态

事务的ACID特性

  • 原子性(Atomicity):事务是数据库的逻辑工作单位,事务中的操作要么都做,要么不做
  • 一致性(Consistency):事务执行的结果必须使数据库从一个一致性状态准移到另一个一致性状态。当数据库只包含成功事务提交结果时数据库处于一致性状态。如果发生故障,有些事务完成部分就被迫中断,而这些事务对数据库的修改已经有一部分写入物理数据库,这是数据库就处于一种不确定状态
  • 隔离性(Isolation):一个事务的执行不能被另一个事务干扰
  • 持续性(Durability):也称永久性,事务一旦提交,对数据的修改就是永久性的

保证事务ACID特性是事务管理的重要任务。

image-20220425111559312

数据库恢复

image-20220425112114505

故障

  1. 事务内部的故障有的可以通过事务程序本身发现,有的是非预期的(占大部分,一般事务内部的故障用来指这类故障),不能由事务程序发现。
    • 事务撤销:在不影响其它事务运行的情况下,强行回滚该事务,即撤销该事务已经对数据库作出的修改。
  1. 系统故障(软故障)是指造成任何系统停止运转的任何事件,使得系统要重新启动。这类错误影响所有正在运行的事务,但不破坏数据库。此时主存内容,特别是数据库缓冲区(在内存)中的内容都已被丢失,所有运行事务都非正常终止
  • image-20220425135103980
  1. 介质故障(硬故障)指外存故障。
  • image-20220425135851742
  1. 计算机病毒
  • image-20220425140045668

恢复

建立冗余数据最常用的技术是数据转储和登记日志文件

数据转储

  • 转储是指数据库管理员定期地将整个数据库复制到磁带、磁盘或其他存储介质上保存起来的过程
  • 备用的数据称为后备副本或后援副本
  • 当数据库遭到破坏后可以将后备副本重新装入,但重装后的副本只能将数据库恢复到转储时的状态

image-20220425141427756

image-20220425141520113

  • 静态转储:在系统中无运行事务时进行的转储操作,开始时处于一致性状态,转储期间不允许对数据库的任何存取操作、修改活动,得到的也是一个数据一致性的状态

    • 转储必须等待事务结束,新的事务也需要等待转储结束。会降低数据库的使用性
    • 实现简单
  • 动态转储:允许转储期间对数据库进行存取或修改,即转储和用户事务可以并发执行

    • 不用等待用户事务结束,也不影响新的事务的运行
    • 不能保证副本中的数据正确有效。在转储期间的某时刻Tc ,系统把数据A=100转储到磁带上,而在下一时刻T**d ,某一事务将A改200,后备副本上的A过时了
    • 需要把动态转储期间各事务对数据库的修改活动登记下来,建立日志文件。后备副本加上日志文件就能把数据库恢复到某一时刻的正确状态
  • 海量转储与增量转储

    • image-20220425143326920

日志文件

日志文件的格式和内容

日志文件是用来记录事务对数据库的更新操作的文件

  • 以记录为单位的日志文件
    • image-20220425144733416
    • image-20220425144759122
  • 以数据块为单位的日志文件
    • image-20220425144940185
作用

用来进行事务故障恢复和系统故障恢复,并协助后备副本进行介质故障恢复

image-20220425145331943

image-20220425145532708

登记日志文件

为保证数据是可恢复的,登记日志文件必须遵循两条原则

  • 登记的次序严格按并发事务执行的时间次序
  • 必须先写日志文件,后写数据库
    • 先写数据库如果没有写上日志文件则无法恢复,而先写日志文件即便没有写数据库,也只是恢复时多执行一次不必要的UNDO操作

恢复策略

事务故障的恢复

  • 事务在运行至正常终止点前被终止,这时恢复子系统应利用日志文件撤销此事务对数据库进行的修改
  • 系统自动完成,对用户透明
  • 系统的恢复步骤:
    • image-20220425150805257

系统故障的恢复

  • 撤销故障发生时未完成的事务,重做已完成的事务。
  • 系统自动完成,无需用户干预:
    • image-20220425151441462

介质故障的恢复

  • 重装数据库,然后重做已完成的事务。
  • 需要数据库管理人员的介入,只需重装最近转储的数据库副本和有关的各日志文件副本,然后执行系统提供的恢复命令即可,具体的恢复操作仍由数据库管理系统完成
    • image-20220425160515386

检查点

两个问题:

  1. 搜索整个日志耗费大量时间

  2. 重做很多不必要的事务,浪费大量时间

具有检查点的恢复技术

  • 增加检查点记录

    • 建立检查点时刻所有正在执行的事务清单
    • 这些事务最近一个日志记录的地址
  • 增加重新开始文件

    • 记录各个检查点记录在日志文件中的地址
    • image-20220425161212277
  • 恢复子系统在登录日志文件期间动态地维护日志

    • 周期性地执行建立检查点、保存数据库状态的操作
    • 具体步骤:image-20220425161531632
    • 恢复子系统定期或不定期地建立检查点,保存数据库状态

使用检查点方法可以改善恢复效率:当事务T在一个检查点之前提交,在恢复处理时没有必要对事务T执行重做操作。

image-20220425162745685

image-20220425162802605

image-20220425163519530

数据库镜像

介质故障对系统影响最为严重,恢复费时;数据库管理人员必须周期性地转储数据库;技术发展,容量成本下降,为了避免磁盘介质出现故障影响数据库的可用性 —> 数据库镜像

image-20220425164212607

image-20220425164235030

image-20220425164328176

频繁地复制数据会降低系统运行效率,实际应用中只选择对关键数据和日志文件镜像。

第十一章 并发控制

封锁

封锁是实现并发控制的一个非常重要的技术

事务T在对某个数据对象操作前请求加锁,在T释放锁之前其它事务不能更新数据对象。

封锁类型

  • 排它锁(写锁)(X锁)
    • 事务T对A加X锁,其它事务不能加任何锁
    • 其它事务不能读取和修改A
  • 共享锁(读锁)(S锁)
    • 事务T对A加S锁,其它事务可以加S锁不能加X锁
    • 其它事务可以读取但不能修改A

封锁协议

对数据对象加锁时需要约定一些规则:

  • 何时申请X锁或S锁
  • 持锁时间
  • 何时释放

三级封锁协议:

  • 一级封锁协议:事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放。(包括正常结束和非正常结束)

    • 可防止丢失修改,并保证事务T是可恢复的

    • 如果仅仅读数据而不对其进行修改,是不需要加锁的,所以不能保证可重复读和不读“脏”数据

    • image-20220426142815432

  • 二级封锁协议:在一级协议的基础上增加事务T在读取数据R之前必须先对其加S锁,读完后可释放S锁

    • 可以防止读出的数据是一个临时数据

    • 防止了丢失修改,还可进一步防止脏数据

    • 不保证可重复读

    • image-20220426143640472

  • 三级封锁协议:在一级协议的基础上增加事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放

    • 防止丢失修改和读“脏”数据,保证不可重复读
    • image-20220426144957099

三级协议的主要区别在于什么操作需要申请封锁以及合适释放锁(即持锁时间)

不同的封锁协议使事务达到的一致性级别不同,封锁协议级别越高,一致性程度越高

image-20220426145232622

加锁的粒度越大,事务之间互相等待的概率越高

活锁

image-20220426150418816

  • 事务T1封锁了数据R

  • 事务T2又请求封锁R,于是T2等待。

  • T3也请求封锁R,当T1释放了R上的封锁之后系统首先批准了T3的请求,T2仍然等待。

  • T4又请求封锁R,当T3释放了R上的封锁之后系统又批准了T4的请求……

  • T2有可能永远等待,这就是活锁的情形

避免活锁的简单办法是采用先来先服务的策略(还有许多调度算法)

死锁

image-20220426150529570

  • 事务T1封锁了数据R1

  • T2封锁了数据R2

  • T1又请求封锁R2 ,因T2已封锁了R2 ,于是T1等待T2释放R2上的锁

  • 接着T2又申请封锁R1 ,因T1已封锁了R1 , T2也只能等待T1释放R1上的锁

  • 这样T1在等待T2 ,而T2又在等待T1 , T1和T2两个事务永远不能结束,形成死锁

死锁的预防

产生死锁的原因是两个或多个事务都已封锁了一些数据对象,然后又都请求对已为其他事务封锁的数据对象加锁,从而出现死等待。预防死锁的发生就是要破坏产生死锁的条件

  • 一次封锁法:每个事务必须一次将所有要使用的数据全部加锁,否则不能继续执行

    • 缺点:

    • 扩大封锁范围,降低了系统的并发度

    • 难以事先精确确定每个事务要封锁的数据对象,为此扩大封锁范围,进一步降低了系统的并发度

  • 顺序封锁法:预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实施封锁

    • 缺点
    • 维护成本高:数据库系统中封锁的数据对象极多,并且随数据的插入、删除等操作而不断地变化,要维护这样的资源的封锁顺序非常困难,成本很高
    • 事务的封锁请求可以随着事务的执行而动态地决定,很难事先确定每一个事务要封锁哪些对象,因此也就很难按规定的顺序去施加封锁

死锁的诊断

与操作系统类似,一般采用超时法或事务等待图法

  • 超时法:一个事务等待时间超过了规定的实现就认为发生了死锁

    • 优点是实现简单

    • 可能误判

    • 时限设置的太长死锁发生后不能及时发现

  • 等待图法:并发控制子系统周期性地生成等待事务图,并进行检测。如果发现图中存在回路,则表示系统中出现了死锁

    • 事务等待图是一个有向图G=(TU)
    • T为结点的集合,每个结点表示正运行的事务
    • U为边的集合,每条边表示事务等待的情况
    • 若T1等待T2 ,则T1 , T2之间划一条有向边,从T1指向T2
    • image-20220426152848882

死锁的解除

选择一个处理死锁代价最小的事务,将其撤销,释放此事务持有的所有锁,使其它事务得以继续运行下去。

事务调度

并行调度的可串行性

可串行调度:对个事务的并发执行是正确的,当且仅当其结果与按某一次序串行地执行这些事务时的结果相同。

可串行性:

  • 是并发事务正确调度的原则
  • 一个给定的并发调度,当且仅当它是可串行化的,才认为是正确调度

image-20220429141943264

image-20220429142157793

image-20220429142208684

image-20220429142041865

image-20220429142051155

冲突可串行化

判断可串行化调度的充分条件

冲突操作:指不同的事务对同一数据的读写操作和写写操作

不能交换的动作:

  • 同一事务的两个操作
  • 不同事务的冲突操作

一个调度Sc在保证冲突操作的次序不变的情况下,通过交换两个事务不冲突操作的次序得到另一个调度Sc’,如果Sc’是串行的,称调度Sc是冲突可串行化的调度

若一个调度是冲突可串行化,则一定是可串行化的调度,因此可以用这种方法来判断一个调度是否是冲突可串行化的

image-20220429143416695

image-20220429143645167

两段锁协议

数据库管理系统普遍采用两段锁协议的方法实现并发调度的可串行性,从而保证调度的正确性

  • 在对任何数据进行读、写操作之前,事务首先要获得对该数据的封锁
  • 在释放一个封锁之后,事务不再申请和获得任何其他封锁

**两段锁的含义:**事务分为两个阶段:

  • 第一阶段是获得封锁,也称为扩展阶段
    • 事务可以申请获得任何数据项上的任何类型的锁,但是不能释放任何锁
  • 第二阶段是释放封锁,也称为收缩阶段
    • 事务可以释放任何数据项上的任何类型的锁,但是不能再申请任何锁

image-20220429144701588

  • 事务遵守两段锁协议是可串行化调度的充分条件,而不是必要条件

  • 若并发事务都遵守两段锁协议,则对这些事务的任何并发调度策略都是可串行化的

  • 若并发事务的一个调度是可串行化的,不一定所有事务都符合两段锁协议

两段锁协议与防止死锁的一次封锁法

  • 一次封锁法要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行,因此一次封锁法遵守两段锁协议

  • 但是两段锁协议并不要求事务必须一次将所有要使用的数据全部加锁,因此遵守两段锁协议的事务可能发生死锁

image-20220429145126414

封锁粒度

封锁对象的大小称为封锁粒度

封锁对象:逻辑单元、物理单元

  • 封锁的粒度越大,数据库所能够封锁的数据单元就越少,并发度就越小,系统开销也越小
  • 封锁的粒度越小,并发度较高,但系统开销也就越大
多粒度封锁

在一个系统中同时支持多种封锁粒度供不同的事务选择

选择封锁粒度,同时考虑封锁开销和并发度两个因素,适当选择封锁粒度

  • 需要处理多个关系的大量元组的用户事务:以数据库为封锁单位
  • 需要处理大量元组的用户事务:以关系为封锁单元
  • 只处理少量元组的用户事务:以元组为封锁单位

多粒度树

image-20220429151638183

多粒度封锁协议

  • 允许多粒度树中的每个节点被独立地加锁

  • 对一个节点加锁意味着这个节点的所有后裔节点也被加以同样类型的锁

  • 两种方式封锁

    • 显示封锁:直接加到数据对象上的封锁
    • 隐式封锁:是该数据对象没有独立加锁,是由于其上级结点加锁而使该数据对象加上了锁
    • 两种方式效果一致
  • 系统检查封锁冲突时,要检查显示封锁和隐式封锁

    image-20220429152216323

  • 对某个数据对象加锁,系统要检查

    • 该数据对象
      • 有无显式封锁与之冲突
    • 所有上级节点
      • 是否与该数据对象上的隐式封锁冲突
    • 所有下级节点
      • 看上面的显式封锁是否与本事务的隐式封锁冲突
意向锁

目的:提高对某个数据对象加锁时系统的检查效率

如果对一个节点加意向锁,说明该节点的下一层节点正在被加锁

对任意结点加基本锁,必须先对它的上层结点加意向锁

意向共享锁(IS锁)

  • 如果对一个数据对象加IS锁,表示它的后裔结点拟(意向)加S锁
  • 例如:事务$T_1$要对*$R_1$中某个元组加S锁,则要首先对关系$R_1$*和数据库加IS锁

意向排它锁(IX锁)

  • 如果对一个数据对象加IX锁,表示它的后裔结点拟(意向)加X锁。

  • 例如:事务$T_1$要对*$R_1$中某个元组加X锁,则要首先对关系$R_1$*和数据库加IX锁

共享意向排它锁(SIX锁)

  • 如果对一个数据对象加SIX锁,表示对它加S锁,再加IX锁,即SIX = S + IX。

  • 例:对某个表加SIX锁,则表示该事务要读整个表(所以要对该表加S锁),同时会更新个别元组(所以要对该表加IX锁)。

image-20220429153307448

具有意向锁的多粒度封锁方法

image-20220429153442070

$No SQL$

关系型数据库的价值

获取持久化数据

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

并发

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

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

  • 事务在处理错误时也有用。通过事务更改数据时,如果在处理变更的过程中出错了,那么就可以回滚(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