数据库恢复技术
保证数据库数据的可靠性。
一个简单的例子:
- 从A账户向B账户转账1000元人民币
- 读取A账户的余额
- 余额足够,扣除一千元
- 读取B账户的余额
- 将余额加上1000元
在扣除余额到余额增加的步骤之间,如果出现了系统性的故障,硬件故障,导致整个流程没有顺利完成,就会导致错账。
简单分析可知出错是因为整个流程并不是原子性的,或者说,想要完成原子性,但是没有对中间错误发生的预案。
事务
事务的基本特性有四个特性,ACID,保证了事务的原子性,一致性,永久性,隔离性。
1. 原子性(Atomicity)
事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。
回滚可以用回滚日志(Undo Log)来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。
2. 一致性(Consistency)
数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对同一个数据的读取结果都是相同的。如果出问题可能会导致不可重复读,幻影读等问题。
3. 隔离性(Isolation)
一个事务所做的修改在最终提交以前,对其它事务是不可见的。在并发的时候使用锁机制来保证隔离性。
4. 持久性(Durability)
一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。
系统发生崩溃可以用重做日志(Redo Log)进行恢复,从而实现持久性。与回滚日志记录数据的逻辑修改不同,重做日志记录的是数据页的物理修改。
事务的目的是保障在任何情况下,不管是正常运行还是发生了系统故障,无论是单用户,串行执行还是多用户并发执行都要保证事务的执行。
关于锁和并发的问题放在下一章讲 [锁与并发](./数据库概论 (八))
日志系统
日志文件是用来记录事务对数据库的更新操作的文件,日志往往以记录为最小单位,或者以数据库定义的数据块为最小单位。
以记录为单位的日志文件的内容
- 各个事务的开始标记(BEGIN TRANSACTION)
- 各个事务的结束标记(COMMIT或ROLLBACK)
- 各个事务的所有更新操作
- 事务标识(标明是哪个事务)
- 操作类型(插入、删除或修改)
- 操作对象(记录内部标识)
- 更新前数据的旧值(对插入操作而言,此项为空值)
- 更新后数据的新值(对删除操作而言, 此项为空值)
重点是为了记录:哪个事务改变了哪个数据块。
登记日志文件的基本原则
- 登记的次序严格按照事务执行的时间次序登记
- 必须先写日志文件,再写数据库
并不是所有的操作都要写日志文件,只有在要把数据写到数据库中的时候才需要先写日志文件,再写数据库
因为可以通过日志文件恢复UNDO失败的数据库操作,如果先写数据库那么如果出现了错误就没有机会恢复刚刚的修改了。
恢复策略
事务运行过程中发生了故障
由恢复子系统利用日志文件撤销刚刚发生的对数据库的修改。此处的恢复操作是对用户透明的,用户不需要干预此处的修复。
- 从日志文件的末尾开始反向扫描日志文件,找到故障事务的数据库修改操作
- 执行修改操作的逆操作
- 继续反向扫描,遇到修改操作则执行2,直到抵达故障事务的开始语句。
系统故障导致的数据库状态不一致
造成不一致的原因
- 未完成的事务的数据已经写入到数据库中
- 已经提交的事务对数据库的修改还在缓冲区中,还没有执行修改
恢复的方法
- Undo 针对未完成的,已经修改的操作
- 反向扫描日志文件,对修改数据库的操作执行逆操作
- 将更新前的值写入到数据库,覆盖数据
- Redo 针对缓冲区中的操作
- 正向扫描日志文件,找到每一个要执行数据库修改的操作
- 将数据重新写入数据库
介质故障
首先要转入最新的,正常的数据库备份副本,使之恢复到最近的正常状态。
- 对于静态存储的数据库副本,装入之后就已经处于一致性状态。
- 对于动态存储的数据库副本,还要装入转储时刻的日志文件副本,使用Undo和Redo机制恢复到一致性状态。
然后对于备份之后的数据,我们要通过存储的日志文件来恢复,重做所有已经完成的事务。
- 扫描日志文件,找出故障发生时已经提交的事务的标志,然后将其记入Redo队列,等待Redo
- 然后正向扫描日志文件,对重做队列中的所有事务进行重做,然后写入数据库