数据库⽇志⽂件--undolog、redolog、undoredolog
卒读是什么意思在谈⽇志⽂件之前,不得不提事务这⼀概念。在某种意义上来说,如果没有事务存在,也就没有所谓的⽇志⽂件。下⾯开始了。
写给奶奶的一封信⼀、事务
1 ACID
数据库事务(Databa Transaction) ,是指作为单个逻辑⼯作单元执⾏的⼀系列操作。 通过将⼀组相关操作组合为⼀个要么全部成功要么全部失败的单元,可以简化错误恢复并使应⽤程序更加可靠。⼀个逻辑⼯作单元要成为事务,必须满⾜所谓的ACID(原⼦性、⼀致性、隔离性和持久性)属性。
原⼦性
(atomicity)
事务必须是原⼦⼯作单元;对于其数据修改,要么全都执⾏,要么全都不执⾏。通常,与某个事务关联的操作具有共同的⽬标,并且是相互依赖的。如果系统只执⾏这些操作的⼀个⼦集,则可能会破坏事务的总体⽬标。原⼦性消除了系统处理操作⼦集的可能性。
⼀致性
(consistency)
事务在完成时,必须使所有的数据都保持⼀致状态。在相关数据库中,所有规则都必须应⽤于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构(如 B 树索引或双向链表)都必须是正确的。某些维护⼀致性的责任由应⽤程序开发⼈员承担,他们必须确保应⽤程序已强制所有已知的完整性约束。例如,当开发⽤于转帐的应⽤程序时,应避免在转帐过程中任意移动⼩数点。
隔离性
(isolation)
由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另⼀并发事务修改它之前的状态,要么是另⼀事务修改它之后的状态,事务不会查看中间状态的数据。这称为可串⾏性,因为它能够重新装载起始数据,并且重播⼀系列事务,以使数据结束时的状态与原始事务执⾏的状态相同。当事务可序列化时将获得最⾼的隔离级别。在此级别上,从⼀组可并⾏执⾏的事务获得的结果与通过连续运⾏每个事务所获得的结果相同。由于⾼度隔离会限制可并⾏执⾏的事务数,所以⼀些应⽤程序降低隔离级别以换取更⼤的吞吐量。防⽌数据丢失
持久性
(Duration)
事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将⼀直保持。
事务必须原⼦地执⾏,即全做或全不做,并且在时间上看起来似乎它是在某个时刻⼀下⼦就执⾏完了⼀样。保证事务正确执⾏是事务管理器的⼯作,这⼀⼦系统完成的功能包括:
1. 给⽇志管理器(下⾯讲述)发信号,使必需的信息能以“⽇志记录”的形式存储在⽇志中。
2. 保证并发执⾏的事务不会以引⼊错误的⽅式相互⼲扰
事务管理器及其相互作⽤如图:
事务管理器将关于事务动作的消息传给⽇志管理器,将关于何时可以或必须将缓冲区拷贝磁盘的消息传递给缓冲区管理器,并传消息给查询处理器使之能执⾏查询以及其他构成事务的数据库操作。
⽇志管理器维护⽇志。它必须同缓冲区管理器打交道,因为⽇志的空间最初出现在主存缓冲区中,在⼀定的时候这些缓冲区必须被拷贝到磁盘上。⽇志和数据⼀样占⽤磁盘上的空间,正如上图所⽰。
最后,该图⽚给出了恢复管理器。当发⽣崩溃时,恢复管理器就被激活。它检查⽇志并在必要时利⽤⽇志恢复数据。和平常⼀样,对磁盘的访问是通过缓冲区管理器进⾏的。
2 事务的原语操作
我们现在详细考虑事务如何同数据库交互。有3个地址空间,它们在重要的⽅⾯相互影响:
三个地址空间
1 保持数据库元素的磁盘块空间
2 缓冲区管理器所管理的虚存或主存地址空间
cad怎么用2 事务的局部地址空间
数据在不同的地址空间之间移动的四种操作原语
INPUT(X): 将包含数据库元素X的磁盘块拷贝到内存缓冲区
READ(X,t): 将数据库元素X拷贝到事务的局部变量t
WRITE(X,t): 将局部变量t的值拷贝到内存缓冲区中的X
OUTPUT(X): 将包含X的缓冲区拷贝回磁盘
⼆、事务⽇志
⽇志保证了数据的持久性和事务的原⼦性。可以简单的认为⽇志是⼀个不断追加⽇志记录的⽂件。单条⽇志记录是⼀段⼆进制缓冲区。下⾯是本⽂会使⽤到的⼏条通⽤的⽇志记录:
标⽰trasaction的开始
孩子玩手机
标⽰transcatoin成功提交,所有对数据的修改都已经成功。由于cache的存在,在⽇志中看到COMMIT并不⼀定意味着 数据的修改都已经持久化。⽇志的⽬的就是保证所有COMMIT的事务的修改在程序程序异常退出的情况下能够保留;所有没有COMMIT 的事务的修改在程序异常退出的情况下都不会保留,就像这些事务根本就没有START⼀样。
事务T的所有修改都不能保留下来,最终体现出来的东西就像事务T根本就没有START过。
⽇志的类型主要有:undo log,redo log,undo/redo log。最经常使⽤的类型为redo log和undo/redo log。 如果系统不出现异常情况, 那么⽇志是没有必要的,因此在讲述的时候⼤部分场景都是指系统异常退出重启,在叙述过程中不在重复描述这个场景前提。
⽇志记录的⼏种形式
(1) <start T> 事务T 开始
(2)<commit T>事务已经成功提交
注:如果要确保<commit T>⽇志记录写到磁盘上,则在commit后⽇志管理器应给出⼀个刷新⽇志命令告诉缓冲管理器把以前没有拷贝到磁盘的⽇志记录强⾏写到磁盘上
(3)<abort T> 表⽰事务不能成功完成
T终⽌,⽇志管理器要保证事务的更新不出现在磁盘上
如果已经写到磁盘上,则要消除之。
UNDO LOG
⽇志内容
undo log就是把所有没有COMMIT的事务回滚到事务开始之前的状态(撤销事务在系统崩溃前可能还没有完成的影响来恢复数据库状态),对于已经commit的事务不做任何处理。 因为对于commit的事务不做任何 处理,那么在写COMMIT⽇志记录之前,事务对数据的修改都已经持久化,如果只有⼀部分持
久化,事务修改的数据会处于不⼀致状态。 另外,由于需要做undo操作,因此⽇志记录中必须包含数据修改前的值,单条的undo log形式为,表⽰在事务T开始运⾏前, X 的值为v;由于对未commit的事务必须进⾏undo操作,那么在对数据库的数据进⾏修改之前,必须先保证事务⽇志已经持久化, 如果⽇志没有持久化,并且最后事务没有commit,那么数据就⽆法回滚到事务开始前的状态。由此可以总结出undo log必须满⾜的两条规则:
U1:如果事务T改变了数据库元素X,那么形如<T,X,v>的⽇志记录必须在X的新值写到磁盘之前写到磁盘中
U2:如果事务已提交,则其COMMIT⽇志记录必须在事务改变的所有数据库元素先写到磁盘之后写到磁盘中,但应尽快。
简要概括规则U1和U2,与⼀个事务相关额内容必须按如下顺序写到磁盘:
1. 指明所改变数据库元素的⽇志记录
2. 改变的数据库元素数据本⾝
3. COMMIT⽇志记录
在这⾥我们可以看到,每个事务commit之前必须把对数据的修改写到磁盘上,这样会导致性能问题, 因为每次事务都回带来⼀次数据⽂件的同步写⼊,⽽使⽤⽇志的主要⽬的就是减少磁盘的写操作。 因此undo log存在较⼤的性能问题,因此在实际中使⽤并不太多。
使⽤undo⽇志恢复
系统重启回放⽇志,只需要从后往前扫描⽇志⽂件,对于所有没有commit的事务按照⽇志记录中的数据做回滚操作。在扫描过程中,恢复管理器记住所有<COMMIT T>或<ABORT T>记录的事务T。同时在向后扫描过程中,如果它看见记录<T,X,v>,则
1 如果T的COMMIT记录已被扫描到,则什么也不做。T已经提交因⽽不需要撤销
2 否则,T是⼀个未完成的事务或⼀个中⽌的事务。恢复管理器必须将数据库中X的值改为v,以防万⼀恰好在系统崩溃前X已经被修改了。
CheckPoint (检查点)
按照上述⽅法回放⽇志,需要扫描所有⽇志⽂件,并且⽇志⽂件不能删除。但是实践上我们可以看到, 如果⼀个事务已经commit了那么之前的 ⽇志记录其实就可以不回放了。因此引⼊checkpoint的概念。
最简单的checkpoint做法是在做checkpoint的时候阻塞所有更新,直到所有未决的事务都commi或者abort, 然后记录。在⽇志回放的时候, 如果碰到就停⽌回放⽇志。阻塞更新当然不是优雅的处理⽅式。 下⾯介绍⼀种不阻塞更新的checkpoint⽅法。
汉服简笔画
⽣成checkpoint的过程为:
1. 记录START_CKPT<T1,T2,...,Tn>,其中Ti表⽰开始⽣成checkpoint的时候未决的事务(没有提交的事务)
2. 等待所有事务提交
3. 记录END_CKPT
⽇志回放的时候,如果⾸先遇到END_CKPT,只需要回放⽇志到下⼀个START_CKPT为⽌,START_CKPT之前的⽇志可以丢弃;如果⾸先碰到的是START_CKPT, 只需要回放第⼀步中记录的所有事务最早开始的地⽅,再之前的⽇志记录可以直接删除。
REDO LOG
redo log是指在回放⽇志的时候把已经commit的事务重做⼀遍,对于没有commit的事务按照abort处理。
⽇志回放并不会处理任何没有commit的事务, 因此,在COMMIT⽇志持久化之前,不能将数据的修改持久化。因为如果数据在COMMIT之前持久化,那么在系统异常退出的情况下,这种部分修改的事务就会处于⼀种不⼀致状态。同时,由于重做事务,因此事务⽇志中必须记录事务修改以后的值。redo log必须满⾜以下规则:
R1:在修改磁盘上的任何数据库元素X以前,要保证与X的这⼀修改相关的所有的⽇志记录,包括更新记录<T,X,v>及<COMMIT>记录,都必须出现在磁盘上。
1. 指出被修改元素的⽇志记录
2. 写COMMIT⽇志
3. 修改数据库元素⾃⾝
使⽤⽇志恢复与checkpoint
对于没有checkpoint的⽇志进⾏恢复需要:⾸先从后往前扫描⽇志:记录所有已经commit的事务,对于没有commit的事务在⽇志末尾追加abort; 然后从前往后扫描⽇志,redo所有已经commit的事务⽇志。
redo log⽣成checkpoint的⽅法:
1. 写⽇志START_CKPT<T1, T2, ..., Tn>;其中Ti表⽰当前正在进⾏还没有commit的事务
2. 当START_CKPT记录写⼊⽇志,把所有已提交事务在缓冲区的新值写到磁盘上去。
3. 写⽇志END_CKPT
在从后往前扫描⽇志⽂件的时候,
当然,可能通过特定的⽅式将⼀个事务的所有⽇志串接起来,减少⽇志的扫描数量。
UNDO/REDO LOG
单独使⽤的Undo/Redo⽇志的缺陷
Undo ⽇志要求数据在事务结束后⽴即写到磁盘,可能增加磁盘I/O数
Redo⽇志要求我们在事务提交和⽇志记录刷新以前将所有修改过的块保留在缓冲区中,可能增加事务需要的平均缓冲区数
如果数据库元素不是完整的块或块块集,在检测点处理过程中,Undo⽇志和Redo⽇志在如何处理缓冲区上存在⽭盾。
⽽undo/redo log可以很好的解决这些问题。undo/redo log是指在⽇志回放的时候像undo log那样回滚所有没有commit的事务; redo log⼀样redo所有已经commit的事务。由于同时要进⾏redo和undo,因此⽇志记录中必须同时记录修改前的值和修改后的值。 <T, X, v, w>,表⽰事务T修改了元素X的内容,修改之前X的值为v,修改之后值为w。同时undo/redo log对于数据和⽇志的持久化 顺序要求很低:
URl :在由于某个事务T所做改变⽽修改磁盘上的数据库元素X前,更新记录 <T, X, v, w> 必须出现在磁盘上
就是说,在读数据库进⾏修改前,必须前必须先写事务更新⽇志。undo/redo log也被称作write ahead log。
当使⽤undo/redo⽇志时,与⼀个事务相关的材料写到磁盘的顺序为:
1. 记录update⽇志
2. 更新数据库(不⼀定持久化,写⼊缓存中)幼儿睡前小故事
3. 在合适的时候写commit⽇志 (在磁盘上任何数据库元素的修改之前或之后,即第3步可能⽐第2步可能早,或者可能晚)
使⽤⽇志恢复与checkpoint
没有checkpoint的⽇志进⾏恢复很简单:⾸先从后往前遍历所有⽇志,undo所有没有commit的事务,并追加abort,同时记录所有 已经提交的事务;然后从前往后遍历⽇志,redo所有已经提交的事务。
redo/undo log的checkpoint⽅式与redo log相似:
1. 写⽇志START_CKPT<T1, T2, ..., Tn>;其中Ti表⽰当前正在进⾏还没有commit的事务
2. 将所有缓存中的脏数据持久化到磁盘
3. 写⽇志END_CKPT
与redo log checkpoint的区别是:在第⼆步,undo/redo log可以将刷新所有的脏缓冲区,包括还没有commit的事务;⽽ redo log在第⼆步的时候只能够将所有已经commit的事务的脏数据持久化到磁盘。
在《Databa Systems: The Complete Book》⼀书中,关于undo/redo log的checkpoint有⼀段这样的描述:
tip翻译事务在不确定其不会在中⽌之前不能写⼊任何值(甚⾄来呢写到哦主存缓冲区也不允许)
>黎安公园