SQL Server事务日志-检查点、redo和undo(2)

使用日志来重做和撤销事务

在事务日志文件存储的内容方面,SQL Server与Oracle也有很大的不同,Oracle的事务日志文件只包含了重做数据,而SQL Server的事务日志文件除了重做文件外,还包含了回滚一个事务所需的undo数据,Oracle的undo数据都保存在undo表空间。所以SQL Server的日志既要用于redo,又要用于undo。

执行rollback操作时,Oracle是使用undo表空间的undo数据进rollback,而SQL Server是使用事务日志中的undo数据进行rollback。

恢复系统使用两个恢复过程,它们都利用日志来找到每个事务Ti更新过的数据项的集合,以及它们各自的旧值和新值

  • redo(Ti):将事务Ti更新过的所有数据项的值都设置成旧值
  • undo(Ti):将事务Ti更新过的所有数据项的值都恢复成旧值
事务回滚

首先考虑正常操作时(即不是从系统崩溃中恢复时)的事务回滚。事务Ti的回滚如下执行。

  1. 从后往前扫描日志,对于所发现的Ti的每一个形如<Ti,Xj,V1,V2>的日志记录:
    a. 值V1被写到数据项Xj中,并且
    b. 往日志中写一个特殊的只读日志记录<Ti,Xj,V1>,其中V1是在本次回滚中数据项Xj恢复成的值。有时这种日志记录称作补偿日志记录(compensation log record)。这样的日志记录不需要undo信息,因为我们绝不会需要撤销这样的undo操作。后面会解释如何使用这些日志记录。

2.一旦发现了<Ti,start>日志记录,就停止从后往前的扫描,并往日志中写一个<Ti,abort>日志记录,表明回滚完成。

系统崩溃后的恢复

崩溃发生后当数据库系统重启时,恢复动作分两阶段进行:

1. 在重做阶段,系统通过从最后一个检查点开始正向地扫描日志日志中所有的操作执行redo。如果日志包括<Ti start>或<Ti commit>或<Ti abort>记录,需要对事务Ti进行重做。如果日志包括<Ti abort>记录还要进行重做,看起来比较奇怪。要明白这是为什么,请注意如果在日志中有<Ti abort>记录,日志也会有undo操作所写的哪些redo-only日志记录。于是,此时重做就相当于对Ti所做的操作进行撤销。这一轻微的冗余简化了恢复算法,并使得整个恢复过程变得更快。

扫描日志的过程中所采取的步骤如下:
a. 将要回滚的事务列表undo-list初始设定为<checkpoint L>日志记录中的L列表。
b. 一旦遇到形为<Ti,Xj,V1,V2>的正常日志记录或形成为<Ti,Xj,V2>的read-only日志记录,就redo这个操作;也就是说,将值V2写给数据项Xj。
c. 一旦发现形为<Ti,start>的日志记录,就把Ti加到undo-list中。
d. 一旦发现形为<Ti,abort>或<Ti,commit>的日志记录,就把Ti从undo-list中去掉。

在redo阶段的末尾,undo-list包括在系统崩溃之前尚未完成的所有事务,即,既没有提交也没有完成回滚的那些事务。

2. 在撤销阶段,系统回滚undo-list中的所有事务。它通过从尾端开始反向扫描日志来执行回滚。
a. 一旦发现属于undo-list中的事务的日志记录,就执行undo操作。
b. 当系统发现undo-list中事务Ti的<Ti,start>日志记录,它就往日志中写一个<Ti,abort>日志记录,并把Ti从undo-list中去掉。
c. 一旦undo-list变为空表,即系统已经找到了开始时位于undo-list中的所有事务的<Ti,start>日志记录,则撤销阶段结束。

当恢复过程的撤销阶段结束之后,就可以重新开始正常的事务处理了。
  下图为一个示例:

《SQL Server事务日志-检查点、redo和undo(2)》

  在撤销阶段从尾端开始反向扫描日志,当发现T2更新A的日志记录时,将A恢复成旧值,并往日之中写一个read-only日志记录。当发现T1开始的日志记录时,就为T2添加一条abort记录。由于undo-list不在包含任何事务了,因此撤销阶段中止,恢复完成。

逻辑undo

有时候我们并不能用插入前的旧值来进行撤销事务。比如有一个事务T1更新过,插入项(V1,R1),然后又被另一个事务T2更新,在同一个结点中插入(V2,R2),甚至在T1完成执行之前就移动项(V1,R1)。在这一点上,我们不能通过用T1执行插入前的旧值替代节点的内容来撤销事务T1,因为这也会撤销事务T2所执行的插入;事务T2可能仍然会提交(或者可能已经提交了)。在这个例子中,对插入(V1,R1)的影响进行撤销的唯一办法是执行一个对应的删除操作。
  要允许操作的逻辑undo,在执行操作之前,事务创建一个<Ti,Oj,operation-begin>,其中Oj是该操作实例的唯一标识。当系统执行这个操作时,它为这个操作所做的所有更新按正常方式创建更新日志记录。于是,对于该操作所做的所有更新,通常的旧值和新值信息照常写出(<Ti,Xj,V1,V2>)。当操作结束时,它写一个形如<Ti,Oj,operation-end,U>的operation-end日志记录,其中U表示undo信息。
  记录关于旧值和新值信息的日志称为物理日志,与此相反,记录关于操作的这类信息的日志称作逻辑日志

有逻辑undo的事务回滚:
  当回滚事务Ti时,从后往前扫描日志,对事务Ti的日志记录做如下处理:

  • 1、在扫描中遇到的物理日志记录像以前描述的那样处理,除了下面描述的需要跳过的那些记录。使用由操作产生的物理日志记录对不完全的逻辑操作进行撤销。
  • 2、一旦系统发现一个<Ti,Oj,operation-end,U>日志记录,就执行以下特殊动作:
  • a.它通过使用日志记录中的undo信息U来回滚该操作。在对操作的回滚中,它将所执行的更新记入日志,就像操作首次执行时进行的更新一样。
  • b.随着对日志的反向扫描的继续进行,系统跳过事务Ti的所有日志记录,直到遇到<Ti,Oj,operation-begin>。在操作回滚的最后,数据库产生的<Ti,Oj,operation-abort>日志记录。

请注意,系统在把回滚中逻辑回滚所执行的更新的物理undo信息(<Ti,Xj,V1,V2>)记入日志,而不是使用一个只读的补偿日志记录(<Ti,Xj,V!>)。这是因为在逻辑undo进行的过程中可能发生系统崩溃,当恢复时系统必须完成逻辑undo;要做到这一点,重启恢复将使用物理undo信息撤销早先的undo的部分影响,然后再重新执行逻辑undo。

  • 3、 如果系统遇到一个<Ti,Oj,operation-abort>日志记录,它就跳过前面所有的记录(包括Oj的operation-end记录),直到它找到<Ti,Oj,operation-begin>日志记录。
      仅在要回滚的事务事先已经部分回滚的情况下才遇到operation-abort日志记录。也就是在本次回滚之前已经做过一次回滚把值恢复为原来的值,所以要跳过此部分不用再进行一次回滚。
  • 4、与前面一样,当遇到<Ti,start>日志记录时,事务回滚就完成了,系统往日志中添加一个<Ti,abort>日志记录。

《SQL Server事务日志-检查点、redo和undo(2)》 有逻辑undo操作的事务回滚

  上图中,T0通过往C中增加100来回滚操作O1; 与此相反,对于数据项B进行了物理的undo。请注意,T1对C执行了一个更新并提交了,它的更新O2在O1的撤销之前执行,往C中增加了200,这个更新保持下来,尽管O1撤销了。
  逻辑日志仅用于undo,不用于redo,redo操作全部使用物理日志来执行。这是因为系统故障之后数据库状态可能反映一个操作的某些更新,而不反应其他操作的更新,这依赖于在故障之前哪些缓冲块已写到磁盘。而逻辑redo和逻辑undo操作都不能在不一致状态的数据结构上执行,即不能有任何操作的部分影响。

SQL Server日志用于undo操作

undo逻辑回滚,就是将数据恢复到更改前的状态,物理变化不会被回滚。比如执行insert,,数据库为之创建了新的extent,此时回滚的话,这个新创建的extent就不会被消除。
  如下图所示是一个简单的insert语句回滚之后的Log Record。我们看到,SQL Server生成了一个Compensation Log Record来反操作前面已经插入的事务,也就是Delete操作。

《SQL Server事务日志-检查点、redo和undo(2)》

  值得注意的是,为了防止这些回滚操作,SQL Server会保留一些空间用于执行回滚(Log Reserve),我们看到LOP_INSERT_ROWS保留的74字节空间被下面的Compensation Log Record所消耗。所以每个事务都在事务日志中保留空间,以确保存在足够的日志空间来支持由显式回滚语句或遇到错误引起的回滚。 保留的空间量取决于在事务中执行的操作,但通常等于用于记录每个操作的空间量。 事务完成后将释放此保留空间。
  Compensation Log record还有一个指向之前LSN的列(Precious LSN),用于回滚,直至找到LOP_BEGIN_XACT的事务开始标记(Compensation Log record的Precious LSN和LOP_BEGIN_XACT的事务的Cuurent LSN相同)。

SQL Server日志用于redo操作

因为日志既要用于Undo,又要用于Redo,因此为了能够成功生成Compensation Log Record,需要日志既记录被修改前的数据,又记录被修改后的数据,比如我们在下图中做一个简单的更新。

《SQL Server事务日志-检查点、redo和undo(2)》

  值得注意的是,如果修改的值是聚集索引键,则由于修改该数据会导致存储的物理位置改变,所以SQL Server并不会像这样做即时更新,而是删除数据再插入数据,从而导致成本的增加,因此尽量不要修改聚集索引键。

Undo/Redo Recovery

当SQL Server非正常原因关闭时,也就是在没有走CheckPoint时关闭了数据库,此时数据库中数据本身可能存在不一致的问题。因此在数据库再次启动的时候,会去扫描日志,进行实例恢复,把尚未写入磁盘的数据根据联机重做日志文件的内容重新写入数据文件(redo),再把未提交却写入磁盘的数据回滚(undo),来保证事务的一致性。
  下图中,我们进行一个简单测试,在启动过程中,首先禁用了CheckPoint以防止自动CheckPoint,然后我们修改数据,不提交,并持久化到磁盘。另一个线程修改数据并提交,但未持久化到磁盘。为了简单起见,我把两个线程写到一个窗口中。

《SQL Server事务日志-检查点、redo和undo(2)》

  此时我们强制杀死SQL Server进程,导致数据本身不一致,此时在SQL Server的重启过程中,会自动的Redo和Undo上面的日志,如下图所示:

《SQL Server事务日志-检查点、redo和undo(2)》

  我们在日志中可以看到的CheckPoint标记如下图所示:

《SQL Server事务日志-检查点、redo和undo(2)》

    原文作者:zoyoto
    原文地址: https://www.jianshu.com/p/bdd36a1f9696
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞