1. 概念
- 什么是原子性?
在同一项事务的处理过程中,事务保证了对多个数据源的修改,要么同时成功,要么同时撤销。
- 什么是持久性?
事务保证所有成功被提交的数据的修改都能正确的被持久化,不丢失数据。
数据只有写入磁盘、磁带等持久化存储器后才会拥有持久性,只存储在内存中的数据,一旦遇到应用程序突然崩溃,或者数据库、操作系统一侧崩溃,甚至是机器突然宕机等情况数据就会丢失。
但现实最大的困难是“写入磁盘”这个操作并不是原子的,不仅有“写入”和“未写入”状态,还客观存在着“正在写”的中间状态。
但是由于崩溃和中间状态都不能消除,所以如果不采取措施,将内存中的数据写入磁盘,并不能保证原子性和持久性。
2. 怎么办?
只能在崩溃后采取恢复的补救措施,也就是“崩溃恢复”。
为了顺利进行崩溃恢复,在磁盘中写入数据就不能像程序修改内存中的变量值那样,直接改变某表某行的某个值,而是必须将修改数据这个操作的全部信息,包括:修改什么数据、数据物理上位于哪个内存页和磁盘块中、从什么值改为什么值,等等,以日志(仅仅以顺序追加的文件写入方式)的形式记录到磁盘中。
只有日志记录全部安全落盘,数据库在日志中看到代表事务成功提交的“提交记录”(Commit Record)后,才会根据日志上的信息对真正的数据进行修改,修改完成后,再在日志中加入一条“结束记录”(End Record)表示事务已完成持久化,这种实现事务的方法被称为“提交日志”(Commit Logging)。
原理分析:
- 只要日志中存在Commit Record记录,即使修改数据时候崩溃了,重启后,可以根据日志信息继续修改数据,保证了持久性。
- 只要日志中不存在Commit Record记录,那么整个事务就是失败的,重启后会看到没有Commit Record记录的日志,此时事务就像没有发生过,保证了原子性。
3. Commit Logging的缺陷
所有对数据的真实修改必须发生在事务提交以后,再此之前,即使磁盘I/O有足够的空闲、即使某个事务修改的数据量非常庞大,占用了大量的内存缓存区。无论何种理由,都不允许在事务提交之前就修改磁盘上的数据。这是Commit Logging成立的前提。但是对提升数据库的性能非常不利。
4. Write Ahead Logging-提前写入日志
就是说,允许在事务提交之前写入变动数据。
按照事务提交点,将何时写入变动数据划分为FORCE、STEAL
- FORCE:事务提交后,又可以细分为:FORCE(变动数据必须同时完成写入)、NO-FORCE;事实上,绝大部分数据库采用的都是NO-FORCE,毕竟只要日志在,变动数据随时可以持久化。
- STEAL:事务提交前,可以分为:STEAL(允许变动数据提前写入)、NO-STEAL(不允许变动数据提前写入);从优化磁盘I/O的角度,允许数据提前写入,有利于利用I/O资源,也有利于节省数据库缓存区的内存。
Commit Logging允许NO-FORCE,不允许STEAL,一旦发生崩溃这些提前写入的数据就成了错误。
Write Ahead Logging允许NO-FORCE,也允许STEAL,给出的解决方案是增加一种 undo log(一般被称为回滚日志)的日志类型,当变动数据写入磁盘前,必须先记录undo log,注明修改了那个位置的数据,从什么值改为了什么值等等,以便在事务回滚或者崩溃恢复时根据undo log对提前写入的数据变动进行擦除;此前记录的用于崩溃恢复时重演数据变动的日志就称为redo log(一般称为重做日志)。
有了undo log,在发生崩溃恢复时,Write Ahead Logging会经历下面三个阶段:
- 分析阶段:找出待恢复事务的集合
- 重做阶段:重演历史(待恢复事务集合),执行完成后,将其移出待恢复事务集合
- 回滚阶段:经过1、2处理剩余的事务集合,都是需要回滚的事务,根据undo log中的信息,将已经提前写入磁盘的信息重新改回去。