数据库复习
CH14 恢复
13.1 恢复的概念
数据库系统中恢复是指让数据库从发生某些“失败”后的不一致的状态恢复到正常的一致状态的行为,恢复的基础是冗余(物理上冗余,非逻辑上)
这些失败包括了:
- 事务失败:包括逻辑错误(事务不满足某些条件不能执行)和系统错误(DBMS强制终止事务,如事务发生死锁)
- 系统崩溃:断电、物理硬件损坏、软件系统(如OS)崩溃,本章假设系统崩溃不会改变非易失存储器
- 磁盘失败:磁盘存储发生错误,本章假设可利用检查和监测磁盘失败
大体上,恢复策略分成两个步骤:
- 正常事务处理时,记录足够的额外信息以供失败时恢复
- 失败发生后,讲数据库返回一致性状态的动作
13.2 存储结构和数据访问
本小节假设讨论恢复问题时的存储结构和数据访问模型
按系统奔溃时是否影响数据存储,将存储器分成易失存储器(主存、cache等)和非易失存储器(磁盘磁带、闪存、非易失RAM等),然后我们假设一种理想的稳定存储器(stable storage),发生任何失败时都不会影响存储数据内容
定义物理块为存储在非易失的磁盘上的块,缓冲块是为存储在易失的主存中的块,而私有空间是指为每个事务T分配的独立于其他事务的虚拟存储区域,那么可以命名一下4种操作:
- input(A):从磁盘中加载物理块到主存
- output(A):把主存中缓冲块写回磁盘
- read(X):从主存中将缓冲块读取到私有空间
- write(X):把私有空间中的本地副本写回到主存
假设在事务中,DBMS在第一次访问块X时调用一次read操作加载到私有缓冲区,事务执行完毕时write(X)写回主存,而事务中间的操作时都只是修改其本地副本
注意,DBMS没有必要在每次write时调用output,这取决于OS的写回策略
13.3 基于日志的恢复
日志是一组记录在理想的稳定存储器上的数据库存储记录,约定:
- 事务Ti开始时,日志记录
<Ti, start>
- 事务Ti执行write操作时,日志记录
<Ti, X, old_value, new_value>
- 事务Ti结束时,日志记录
<Ti, commit>
- 任何时候最多只有一个事务活跃
基于日志的恢复有两种策略,推迟数据库修改和立即数据库修改
(1)推迟数据库修改
推迟数据库修改模式下,数据库的修改操作仅记录在Log中而不真正的实施write操作,直到partial committed之后才write
定义在失败发生时的redo和undo操作:
- redo:按照Log中的
<Ti, X, old_value, new_value>
再一次写入新值new_value - undo:按照Log中的
<Ti, X, old_value, new_value>
撤销新值写入,即写入old_value
由推迟数据库修改模式的定义知,未记录<Ti, commit>
的事务不执行任何恢复操作,已记录<Ti, commit>
的事务顺序地执行redo操作(其实这种模式下old_value是没有必要记录的)
(2)立即数据库修改
立即数据库修改模式和推迟数据库修改模式相反,事务未commit之前就允许修改数据库,write-ahead logging rule(WAL规则)规定log记录必须在数据库写入之前完成
立即数据库修改下,未记录<Ti, commit>
的事务必须反序地执行undo操作让数据库返回到一致性状态,而已记录<Ti, commit>
的事务顺序地执行redo操作
(3)检查点
每次redo和undo时都遍历整个Log并完成所有事务的redo和undo的话,开销十分巨大,并且对于已经commit后output写入磁盘的redo是没有任何意义的,因此在Log引入<checkpoint>
语句
DBMS周期性地做检查工作,把所有commit的事务(已write入主存)从主存中写回到磁盘中,并在日志中记录一条<checkpoint>
;注意检查时,可能某个事务还未commit
有了检查点的数据库恢复,每次错误时只需恢复最近的事务即可:
- 反向遍历日志,直到找到第一个
<checkpoint>
- 继续反向遍历,找到最近一个未commit的事务起点
<Ti, start>
- 从该句开始执行响应的redo和undo操作
那么对于下面的时序图,发生失败时有(立即数据库修改模式):
- T1被忽略
- redo T2和T3
- undo T4
13.4 影子数据库
除了基于日志的恢复,还另一种恢复策略——影子数据库(Shadow Database),它有以下设定
- 假设任何时候只有一个事务活跃
- db_pointer指针总是指向数据库当前的一致性副本
- 所有的update都是在数据库的影子拷贝(shadow copy)上完成的,只有当事务partial committed之后才把影子拷贝中的副本写入磁盘中,并且更新db_pointer
- 事务失败时转向db_pointer指向的一致性的数据库,当前影子数据库被直接删除
- 假设磁盘不会失败
影子数据库的拷贝策略运用在大型数据库上十分低效
13.5 并发事务的恢复
13.3中介绍的基于Log的恢复是针对单活跃事务而言的,下面进行更多的假设已完成对并发事务的恢复:
- 所有的事务共享一份存储缓冲和一份日志记录
- 一个缓冲块中可以有多个事务修改后的数据
- 并发控制严格遵循两阶段锁(下一章详细描述),保证未commit的事务间不可见
- 并发事务的Log可以交织在一起
- 检查点发生时,可能有多个事务正处在活跃状态,记录
<checkpoint, L>
,L是检查点发生时活跃的事务列表 - 当失败发生时执行一下动作:初始化
undo_list
和redo_list
为空表,倒序找到第一个<checkpoint, L>
,对于L中每一个事务的update按Log记录反序加入undo_list
、正序加入redo_list
- 执行
undo_list
和redo_list
中的操作
13.6 缓存管理
(1)Log记录缓存
如果把Log缓存在主存中,那么当缓存已满或log force操作时才把Log记录(全部)写入磁盘(这样多条log记录可以一次output,减少I/O开销),此外Log记录缓存还必须遵从以下约定:
- log记录必须顺序地output进磁盘
- 事务Ti只有当
<Ti, commit>
写入磁盘后才能进入Committed状态 - log记录必须比相应修改后的数据先写入磁盘
(2)数据库缓存
数据库缓存和Log记录缓存不一样的是,它是分块划分缓存的,当缓存满时是选择缓存块被新块替换(若修改需要写回磁盘),而不整个缓存写回
13.7 非易失存储器的失败
上述对恢复的讨论仅包含易失存储器的部分,非易失存储器的恢复也利用了相似的分层存储器思想:增加一种dump
操作,它下一级是理想的稳定存储器(可以假想为磁带等层次更低的存储介质)