本实验的redo log和undo log均为物理日志,共用同一份日志文件,也就是redo/undo log。undo log采用物理日志来回滚一个事务的实现为:在内存中维护一个活跃事务表,记录每一个活跃事务的最后一条操作的LSN位置,根据这个位置获取日志记录log_record,每个record_log都有该事务的上一条record_log的lsn,直到读取到事务的begin_log_record为止,就可以依次将一个事务的全部操作撤销。
在mysql中redo log是物理日志,而undo log是逻辑日志

物理日志记录的是页面数据的更改
例如在本实验中,一条插入日志的内容如下:
事务id,对象id,当前页id,页内的slot id,改动数据所在页内偏移,记录的数据大小,记录的数据内容

在实验框架中,LSN 使用的是日志记录在日志文件中的位置表示,这种表示方法可以方便地通过 LSN 直接获取日志,无需再单独存一份 LSN 到日志位置的映射表。

首先说说幂等性:即同个操作无论做了多少遍,结果都是一样的,有f[f(x)] = f(x),为什么需要保证幂等性呢?
考虑一个问题:如果某个时间点发生故障,日志持久化了,但是实际操作有部分已经完成,但是有部分还没完成,那么进行故障恢复时扫描redo log重做了已经完成的部分,就会导致一个操作执行了多遍,比如在页内插入多条相同的数据。所以需要幂等性来保证无论重做多少遍,都不影响结果的正确性。
redolog为物理日志记录的是数据的变更位置,比如insert 操作实际执行的是把数据写到对应位置,那么只会重新在这个位置上再覆盖写一遍,而delete操作实际上是把对应的记录的删除flag标记为true(本实验的框架中),也只重新设置一遍,均不改变正确性。
log_record的redo函数只需使用日志来重新执行一遍操作,而物理日志方法来重做是能够保证幂等性的,因为日志记录了该元组位于页面的偏移量,只需在同个地方再插入一遍,所以如果原位置有数据,也只会进行覆盖。
另外,update的实现是由删除旧元组+插入新元组来实现的,所以update的日志即InsertLog和DeleteLog来实现。

事务的回滚:实验中Log_record包含有当前事务的上一条日志lsn,因此找到事务的最后一条lsn地址,即可遍历该事务的所有lsn。从后往前,对每个操作进行undo,插入操作是对数组做deleted标记为true,删除操作则deleted改为false,比较简单。

同时,在实验中可以发现物理日志的写入特点,如果有多个事务并发执行时,它们会交叉将日志记录写入缓冲区,不同于逻辑日志,逻辑日志只有在一个事务提交时才将完整记录进行一次写入,即逻辑日志的写入是按事务为单位的。

ARIES恢复算法

首先要理解checkpoint,数据库在周期性将当前数据库的快照进行保存,以便数据库发生故障时进行恢复所需重做操作数量大大减少。
有两种实现,一种是停机保存,需要暂停服务,保证没有正在运行的事务,同时把所有脏页刷回磁盘。
实验采用的第二种,即不停机保存,实现方式是把当前活跃事务表和脏页表保存起来。那么在故障恢复时,与停机保存从checkpoint点开始重做不同,不停机保存需要从min(Rec LSNi)位置开始重做(即比较所有脏页的rec lsn,选出最小的lsn位置)
图片
Dpt(Dirty page table):记录了每个脏数据页到第一条日志lsn(即RecLSN)的映射,每次记录日志时,均会判断该日志所改动的数据页是否存在于drity page table中,没有则添加,一旦进行flushpage后,首先把此页范围内的日志写到磁盘,同时会把该页从脏页表中移除。
ATT(active Transaction table):记录当前活跃事务到该活跃事务最后一条lsn的映射,用于撤销操作
Master Record记录的是begin checkpoint log 的lsn

图片
ARIES 通过记录一条 Checkpoint Begin Log 来明确这个「某一时刻」的位置。
详细步骤如下:

  1. 先记录一条 Checkpoint Begin Log。
  2. 拷贝att和dpt
  3. 记录一条 Checkpoint End Log。Log 内容包括 ATT 和 DPT 的信息。然后把 Log 刷盘。
  4. 把 Checkpoint Begin Log 的 LSN 信息记录到 Master Record 中。

Recovery 的时候,先从 Master Record 中找到 Checkpoint Begin Log 的位置,然后找到 Checkpoint End Log 的位置,恢复出 ATT 和 DPT 的初始状态
Alt text
本实验中,开始检查点日志和结束检查点日志连在一块,中间无其他事务日志。在redo中,应从所有脏页中最小的Rec lsn开始进行重做;undo则是需要在活跃事务表中找到事务的最后一条日志,然后依据该日志的prev_lsn找到前一条日志依次撤销操作。完成这些步骤后,数据库即可恢复原状。