数据库事务的实现(一) 故障恢复 (undo日志)
前言
事务是数据库中的重要组成部分。针对于事务本身的介绍,大家可以google下其他文章。本篇文章的目的不在于介绍事务是什么,而重点想描述的是事务是如何实现的。所以本篇文章及接下来的几篇文章都是描述数据库系统中事务的实现,希望通过这几篇文章将自己的知识做一下总结,当然,如果在总结的同时,可以给各位朋友带来相应的益处那是最好的了。如果文章有不妥之处,欢迎各位朋友指正。
简介
保证数据的一致性是数据库的一个最最基本的功能,那数据库在机器down机或者出现其他意外的情况下是如何去保证数据库的数据的一致性的呢?本文主要围绕这个话题,介绍undo日志和redo日志这两种保持数据一致性的机制。undolog和redolog是数据库的两种日志文件,数据库正是利用这两种日志文件去实现数据的一致性。
数据库架构简介
要介绍数据库一致性的实现机制,自然少不了要介绍下数据库的整体架构,这里画一个简图来介绍下数据库的架构。
我们将数据库简化,主要分这么几块:
- 查询处理器,主要负责针对于查询sql的解析、执行计划的选择等等。
- 事务管理器,事务是数据库操作的最小单位,事务管理器主要针对于分配事务id等等管理工作
- 日志管理器
- 恢复管理器
- 缓冲管理器,这个大家也清楚,数据库中的写操作都是在缓冲器中完成,然后再flush到硬盘上。
- 硬盘数据,日志 无论是数据库的数据、还是日志文件,都是最终要写到硬盘上做持久化存储的。
下面就针对于上面说的这几块组成部分,来描述下数据库是如何做灾难恢复的。这一篇主要还是讲undolog,下一篇再讲redolog。
undo日志简介
undo日志,顾名思义,就是撤销日志,也就是说,这个日志里面记录了相关的撤销操作。通过刚才的数据库架构简图,我们也可以看到,针对于写数据等处理主要还是在内存中进行,既然在内存中进行,那就会出现由于机器宕机导致的丢数据问题。那数据库是如何通过undo日志去保证数据的一致性的呢?
要描述这个问题,我们需要先定义几种操作。假设我们现在要做这样一件事情,需要把某条数据X从数据库中读出来,然后再去改变他的值,改为Y,然后再写回去。好,这样一个操作,数据库可能要走这样几个流程,首先会看缓冲区里有没有,假设有则直接将数据返回,我们称这样一个过程为Read(X),假设缓冲区里没有,那就需要先从硬盘读到缓冲区,然后再返回给用户,那我们定义从硬盘读到缓冲区的过程为Input(X),也就是说,如果缓冲区里没有,那数据库要先经过一个Read(X),然后再经过一个Input(X),然后再经过一个Read(X)。那修改时也一样,数据库要修改缓冲区的内容,那这个操作我们成为Write(Y).还要经过一个从内存刷到磁盘的过程,这个过程我们成为output(Y)。好,有了这几个定义,我们就挨个从这几个过程去分析,如果数据库再这中间某个过程挂了,如何去保证数据的一致性。
在介绍前,先简要说下undolog日志的格式,undolog日志的格式是这样的 <T,A,X>,T就代表事务ID,A就代表某一行的某一列,X就代表原先的值。也就是说这条日志就代表T这个事务时,A原先的值为X,对,undolog仅仅记录原先的值,他并不关心你把它改成了多少,他关心的就是原先是多少,因为将来只是做撤销工作。除了这个之外,undolog中还记录start ,意为开启一个事务,commit,意为提交一个事务。大体我们就可以先抽象成这几个。
那比如我们要做上面那个问题,即读取一个值再修改(假设不在缓冲区中),那就要经过以下几步:
见表格:
编号 | 操作 | undolog |
---|---|---|
1 | – | start |
2 | Read(X) | – |
3 | Input(X) | – |
4 | Read(X) | – |
5 | Write(Y) | – |
6 | – | <T,A,X> |
7 | flush undolog | |
8 | Output(Y) | – |
9 | … | … |
10 | commit | – |
11 | – | end |
10 | flush undolog |
我们看上面这个表格,下面我就解释下这个表格。 首先,我们需要明确的一点是,无论是操作数据库中的数据,还是日志,都是先在内存中操作,然后flush到硬盘上。这一点是毫无疑问的。
前4步应该都容易理解,一开始在undolog中需要记录一个start的标志位,然后2、3、4步都是读取数据库的内容,第5步往内存中写,将X的值改为Y,然后第6步undolog中会记录下事务T中A的原先值是X,那到了第7步呢? 是到底是应该先将undolog进行flush,还是应该output后再flush呢?
我们做个假设,假设output后再进行log的flush,如果恰好就在output后,数据库down机了,那这样的结果显而易见,日志文件里并没有记载undolog(因为没有flush到硬盘),无法重做所以就会导致数据不一致。所以要在output后进行undolog的flush是不可取的。
那我们看下上面这个顺序的意义,假设在第6-7步之间宕机了,也就就是在还未flush undolog时已宕机,那不会影响数据的一致性,因为本来数据就没有写到硬盘。如果是在第7-8步间宕机,虽然数据库的数据没有写到硬盘,但log已经flush了,那这时会通过flush后的log重做一遍,因为系统不知道这个log做过了没有,不过即使是重做一遍也不影响最终的数据一致性,也只是将原先的数据又重新写了一遍而已,就是从X写为X,这个不影响数据库一致性,undolog是幂等的,也就是做几次的结果都是一样。所以上面这个顺序才是合理的。
通过undolog进行数据恢复
既然有了undolog,那我们看下数据库是如何通过undolog进行数据恢复的。这时上面架构简图里的恢复管理器就起了作用了,恢复管理器会扫描undolog,找出没有end掉的start,因为从上面的顺序中我们可以看出,“end”记录flush到log中是在事务提交后才flush的,所以,只要是有了end记录了,就说明了这个事务本身已经结束了,数据一致性已经可以保证了。所以恢复管理器这时是扫描没有end配位的start,然后从start开始往后,根据undolog中记录的先前的值进行重做。不过根据我们刚才这个模型会发现,在恢复管理器进行重做时,是不能有其他的写入的,也就是现在的写入应该是夯住状态的。而且还有个问题,恢复管理器需要从头开始对于undolog进行扫描,其实这是不必须的,完全可以有个checkpoint(代表在这之前的数据已经可以保证数据一致性了)的,那恢复管理器只需要找到最后一个checkpoint,然后从checkpoint往后做就可以了。
针对于以上这两个问题,我们下一篇文章数据库事务的实现-故障恢复(二)(undo日志检查点)再讨论,这篇就先到这吧。要干点别的了。
希望本文会对大家有帮助。