保证事务的原子性和持久性需求:

原子性:

  • 事务运行期间不刷盘,系统故障重启后保证原子性。
  • 事务运行期间刷盘,系统故障重启后需回滚该事务。

持久性:

  • 事务完成时刷盘,故障系统重启后自动保证持久性。
  • 事务完成时不刷盘,故障系统重启后需要重做该事务。

数据库故障类别

事务故障:资源冲突和死锁等导致执行失败

系统崩溃:数据库或操作系统故障导致进程意外终止

磁盘故障:磁盘损坏导致读取异常

自然灾害:自然灾害对数据库物理环境造成破坏

数据库恢复机制架构

场景的解释和应对策略:

  • 无故障事务回滚:原子性,撤销该事务已做操作
  • 故障回滚:原子性,撤销该事务已做操作
  • 系统故障:原子性 持久性,已经提交的事务通过日志恢复到持久存储,未结束的事务被撤销
  • 系统崩溃不能重启:持久性,一主多备
  • 磁盘故障:持久性,数据多备份
  • 自然灾害:持久性,异地多机容灾

存储机制应对策略

单机:

  • 单个机器单个存储
  • 单机数据库恢复解决:故障回滚,无故障事务回滚,系统故障

单机多数据备份:

  • 但机器,多磁盘同步(避免短板,使用一致性协议:大部分完成即可)
  • 解决:磁盘故障

一主多备:

  • 一主机多备机,内存中的日志相互同步(日志一致保证内容一致)
  • 解决:系统崩溃不能重启

异地容灾:

  • 多地区不同机器,日志同步传输。
  • 解决:自然灾害

对恢复快慢的评估(高可用指标)

通用高可用指标:

  1. 平均故障间隔时间 (MTBF, Mean Time Between Failures)
    • 系统在相邻两次故障之间正常运行的平均时间。
  2. 平均恢复时间 (MTTR, Mean Time to Repair)
    • 系统发生故障后,恢复到正常运行状态所需的平均时间。
  3. 平均损坏时间 (MTTF, Mean Time to Failure)
    • 系统出现损坏或永久性失效的平均时间。

数据库容灾指标:

  1. 恢复点目标 (RPO, Recovery Point Objective)
    • 系统发生故障后,能够容忍的数据丢失量,即可以恢复的最远时间点。
  2. 恢复时间目标 (RTO, Recovery Time Objective)
    • 系统发生故障后,能够容忍的最长恢复时间,即业务中断时间。
  3. n个9(例如“几个九”可用性)
    • 系统的可用性用n个9表示。
      • 99%:一年最多不可用时间为 365 * 24 * 60 * 0.01 = 5256分钟,即87.6小时。
      • 99.9%:一年最多不可用时间为 365 * 24 * 60 * 0.1% = 525.6分钟,即8.76小时。

崩溃恢复策略设计

简略说明一下崩溃恢复的情况和处理:

在系统崩溃时,事务的状态有三种:已经提交commit,abort事务,未完成的事务。

  • 已提交commit:已刷脏:正确,未刷脏:重做
  • 中止abort事务:已刷脏:正确,未刷脏:重做
  • 未完成事务:已刷脏:回滚,未刷脏:正确

原子性保证:

NO-STEAL(非窃取):

  • 未结束事务不能将脏页写入磁盘,没有原子性问题。
  • 事务执行过程中不能刷新磁盘,必须占有较大的缓冲区空间,不利于多个事务的并发执行

STEAL(窃取):

  • 未结束事务能将脏页写入磁盘,影响原子性,需要回滚。
  • 利用Undo日志撤销事务,Undo日志记录撤销事务所需的内容

持久性保证:

Force(强制):

  • 已完成事务强制将脏页写入磁盘,不存在持久性问题。
  • 每次事务提交都必须刷新脏页,消耗大量IO读写资源

No-Force(非强制):

  • 已完成事务不强制将脏页写入磁盘,影响持久性,需要重做。
  • 利用Redo日志重做事务,Redo日志记录事务对数据库的所有影响

每种各两个选择,一共有四种选择,决定了是否有undo和redo日志

数据页面写回磁盘时机:

  • 数据库关闭时,缓冲区中的所有脏页需要写回磁盘
  • 缓冲区中的数据页面已经满了,如果需要继续读入数据页面,就必须将被替换的脏页写回磁盘
  • 数据库会设置一个单独线程定时刷脏:全量、增量

数据库日志

日志记录是数据库活动的最小单位,每条记录反映一次操作,包含数据更新和事务的开始/结束信息。日志一旦写入磁盘便不会被修改,保证了高效的顺序写入,这为高可用恢复机制提供了基础

日志分类

按照功能:

  • undo回滚日志
  • redo重做日志

按照性质:

  • 物理日志
  • 逻辑日志
  • 物理逻辑日志

Undo回滚日志

格式:<T, X, Vold>

  • T:事务唯一标识符
  • X:数据项
  • Vold:数据项修改以前的值

当事务T修改数据项Wt(X)后产生

Redo重做日志

格式:<T, X, Vnew>

  • T:事务唯一标识符
  • X:数据项
  • Vnew:数据项修改以前的值

当事务T修改数据项Wt(X)后产生

预写日志WAL

  • 日志必须比数据更早的写入磁盘
  • 日志写回磁盘的顺序必须和日志生成的时间相一致
  • 对于事务原子性保证,每当页面写回磁盘时,和事务相关的undo日志需要先写回磁盘
  • 对于事务持久性保证,每当事务提交的时候,和事务相关的redo日志需要先写回磁盘

日志记录方案

概念与实例

UPDATE Student SET Sname=“Mike” WHERE Sno = “1”;

  • 逻辑日志:记录事务中高层抽象的逻辑操作:(UPDATE DELETE INSERT的操作文本信息)

< T’, Query=“UPDATE“, Student SET Sname = Mike WHERE Sno = 1” >

  • 物理日志:记录数据库中具体物理变化:(第十个页面第100偏移量的值修改)

< T’, Table= Student, Page=99, Offset=4, Before=James, After=Mike > < T’, Index=X_PKEY, Page=45, Offset=9, Key=(1,Record1) >

  • 物理逻辑日志:结合了物理日志和逻辑日志混合方法

<T’, Table= Student, Page=99, ObjectId=1, Before=James, After=Mike > < T’, Index=X_PKEY, IndexPage=45, Key=(1,Record1) >

日志性质

  • 幂等性:一条日志记录执行多次和一次结果一致。例如 x = x + 1不幂等.

物理日志满足,逻辑日志不满足

  • 失败可重做性:一条日志执行失败后,是否可以重做一遍达成恢复目的。插入记录时若发生故障,重做插入可以恢复数据库状态。

物理日志满足,逻辑日志不满足(插入数据成功但插入索引失败,再次执行逻辑插入可能会导致不一致)

  • 操作可逆性:这是指执行某个操作后,是否可以通过逆向操作恢复到原来的状态。

物理位置不满足,逻辑日志满足(数据偏移位置可能后续被更改)

日志选择

逻辑日志不具有幂等性和失败可重做性,说明不能使用逻辑日志作为redo日志。

物理日志不具有可逆性,无法处理数据的位置变化,不能回滚。

则redo日志要用物理日志,则undo日志要用逻辑日志。

数据库恢复算法

影子拷贝方法(NO-STEAL + FORCE)

当事务开始时,系统不会直接对原始数据进行修改,而是将需要修改的数据进行拷贝,创建一个“影子副本”。再对影子副本进行一个数据修改。当事务成功完成并准备提交时,系统将影子副本更新到实际的存储中。这样,修改操作才真正生效。

缺点:效率低,难以支持事务并发,IO量大。

基于undo日志的恢复(STEAL + FORCE)

依靠undo日志完成回滚事务。

需要完成:找到所有未完成的事务 - 回滚未完成的事务 - 写入该事务中止的日志

执行流程:

  • 写入日志开始记录 <T, start>
  • 在修改数据项X之前,写入日志记录<T, X, Vold> (修改过的脏页允许刷盘)
  • 提交事务,将关联脏页写入磁盘,将脏页相关undo日志刷盘。(页面如果被淘汰,则要与对应日志一起刷盘)
  • 结束事务,如果是提交,则写入<T, commit> 如果是abort 则写入 <T, abort>
  • 回滚日志,反向扫描undo日志进行回滚,将回滚中更新的脏页刷盘,写入

1.png

缺点:

每次事务提交都需要强制刷盘,造成随机页面读写多,性能差

难以实现主备之间同步

基于redo日志的恢复(NO-STEAL + NO-FORCE)

依靠redo日志完成回滚事务。

需要完成:找到所有已提交的事务 - 重做这些已完成的事务 - 写入该事务结束的日志

执行流程:

  • 向日志中写入事务开始记录
  • 在修改之前,向日志中写入redo日志记录<T, X, Vnew>(未提交事务修改过的脏页不允许刷盘)
  • 提交事务,写入事务提交记录,并且将日志刷盘。事务T关联的脏页(且该脏页无相关未提交事务)允许刷盘。
  • 结束事务,如果是Abort,让缓冲区中的修改的页面失效
  • 回滚事务,废弃T相关的脏页,写入中止

2.png

恢复流程:

  • 从数据库日志末尾向前扫描日志,其中的事务T
  • 如果已经出现, 则该事务已被提交,需要重做,将数据项X置为Vnew,如果已经刷盘则不需要(通过checkpoint判断是否刷盘)
  • 如果出现 或者没有 提交记录,那么就不需要处理相关日志记录。
  • 扫描结束后,对每个未完成的事务T,在日志中写入一个记录并刷新日志

缺点:

事务执行期间不能刷盘,造成内存空间占用大;buffer缓冲池满时,由于不能淘汰未完成的事务,需要等待。

事务并发受限:T1修改了A并提交,T2修改了A未提交,是否允许刷盘A

基于undo/redo日志的恢复

需要依靠undo日志处理事务回滚,需要依靠redo日志处理事务重做

需要完成:找到所有需要重做以及需要回滚的事务 - 重做这些已完成的事务 - 回滚未结束的事务

执行流程:

  • 向日志中写入事务开始记录
  • 在修改之前,写入Undo日志和Redo日志记录<T, X, Vold, Vnew>(修改过的脏页允许刷盘)
  • 提交事务:写入事务提交记录,并且将日志刷盘,页面可不刷盘
  • Abort事务:写入事务中止记录,并且将日志刷盘,页面可不刷盘
  • 回滚事务,反向扫描T相关的undo日志,执行回滚,写入该事务中止的记录,并将日志刷盘

恢复流程:

  • 分析阶段:系统从日志起始位置开始扫描整个日志,找出需要重做和需要回滚的事务

在扫描过程中出现的日志记录而没有,那么该事务在数据库崩溃的时刻是未结束的,需要被回滚。(标注回滚)

– 在扫描过程中出现了,那么事务是已经完成,需要被恢复子系统重做。(标注重做)

  • 重做阶段:系统按时间顺序正向扫描日志,如果出现了一条标注重做的日志记录,系统便重做它

重做所有日志更新记录(重放历史)

  • 撤销阶段:从日志末尾反向扫描整个日志,如果出现了一条标注撤销的日志记录,那么系统会撤销它。

一旦事务撤销完成(即扫描中遇到了),数据库会自动写入,代表该事务已经回滚完成。

由于undo日志是逻辑日志,不能多次执行一条undo日志,撤销过程中需要记录某条undo日志(补偿日志)是否被执行过。

  • 补偿日志:Undo日志的redo日志

每次执行undo日志记录后,数据库需要向日志中写入一条补偿日志记录(compensation log record,CLR),记录撤销的动作

CLR实现了undo日志的redo,记录已经undo的日志,保证undo不被重复执行

检查点CheckPoint机制

检查点的使用方法:

  • 在检查点之前完成(commit/abort)的事务不需要处理
  • 在检查点之后commit/abort的事务需要重做;
  • 所有未完成的事务(不含commit/abort)需要回滚。

检查点的记录方法:

  • 停止接受新的事务或修改请求,确保没有新的脏数据产生。
  • 将当前所有未持久化的脏数据页写入磁盘,更新对应的数据文件。
  • 记录检查点位置。
  • 恢复接受新的事务或修改请求,继续正常的数据库操作