本文概要
本文分两部分,
第一部分概念介绍,重在理解。
第二部分通过MySQL Innodb中的具体实现,加深相关知识的印象。
本文的原意是一篇个人学习笔记,为了避免成为草草记录一下的流水账,尝试从给人介绍的角度开写。但在整理的过程中,越来越感觉力不从心,一是细节太多了,原以为足够了解的一个小知识点下可能隐藏了很多细节;二是内容与范围的取舍,既想有点技术性避免空谈,又不想陷入枯燥冗长的小细节描述。几番折腾,目前的想法把坑填上,能写完就不错了,你读起来有不顺或错误的地方请见谅,欢迎反馈。
1. 概念与理解
Redo与undo并非是相互的逆操作,而是能配合起来使用的两种机制。
说是两种机制,其实都是日志记录,不同的是redo记录以顺序附加的形式记录新值,如某条记录<T,X,V>,表示事物T将新值V存储到数据库元素X,新值可以保证重做;
而Undo记录通常以随机操作的形式记录旧值,如某条记录<T1,Y,9>,表示事物T1对Y进行了修改,修改前Y的值是9,旧值能用于撤销,也能供其他事务读取。
Redo用来保证事务的原子性和持久性,Undo能保证事务的一致性,两者也是系统恢复的基础前提。
1.1 Redo
一个事务从开始到结束,要么提交完成,要么中止,具有原子性。而反映在redo日志中可能需要若干条记录来保证,如:
<T0 start>
<T0,A,500>
<T0,B,500>
<T0 commit>
这里的某条redo记录不是事务级别的,一般对应的是事务中的一些关键步骤。如Innodb执行事务时会拆分为很多小事务,每个小事务产生某条redo记录。
而通过几个数据库原语能更一般性的描述redo记录:
Input(X):将X值从存储介质读入缓冲区
Read(X,t):将X值从缓冲区读入事务内的变量t,如果缓冲中不存在,则触发Input
Write(X,t): 将事务内的t写入到缓冲区X块,如果缓冲中X不存在,则触发Input(X),再完成write
Output(X):将缓冲区X写入到存储中
所以上面的redo记录用原语表示如下:
很明显,现实中的redo日志大多不是这样孤立的,更多的是多个事务交织在一起的,错误也随时能发生,从小到数据格式错误到机房被导弹炸了。
下面通过3个redo日志来讨论:
(a) 在日志中只有部分记录,可能事务在执行时系统发生了崩溃,这时需要根据日志重做(以下统一使用术语redo)。
(b) 日志中T0已经提交了,必须要对T0 进行redo,而部分T1也需要redo
(c) 日志中T0已经提交了,必须要对T0进行redo,而T1虽然abort也需要redo
可能有人有疑惑,commit的事务确实要redo,但进行到一半未提交的事务及后来abort的事务可以不必进行redo。确实,在日志中的每一个事务最终应该或者有一条commit记录,或者有一条abort记录,完全能筛选出目标事务再redo,但这样增加了redo阶段的复杂性,所以是根据日志统一redo,之后的撤销工作交给undo来进行。这也是redo具有事务无关性的一个体现。
1.2 Checkpoint
检查点的引入有好几个方面的原因。
原则上,系统恢复时可以通过检查整个日志来完成,但无论redo还是undo,当日志很长时:
1.搜索过程太耗时
除了上面这点,针对redo而言还有:
2.尽管redo是幂等的,大多数需要重做的事务已经把更新写入,对其重做不会有不良后果,但这会使恢复过程变得很长。
针对undo日志:
3.一旦事务commit日志记录写入磁盘,逻辑上而言本事务的undo