最近看到公司DBA写的一片关于redo机制的文章,读过之后,对于Oracle Redo在数据库中的原理及应用有了一些认识。之前在一篇blog 浅析Redo日志的6种状态 中也简单介绍了关于redo日志组在数据库中的常见状态,并提到了Oracle会使用redo log来进行数据库的故障恢复。
数据库回滚前的检查
对于数据库来讲,在进行故障恢复时,需要首先检查故障前数据库是否存在事务未提交,或事务已提交但数据库中存在脏数据块的情况,根据不同的场景数据库会进行事务回滚。
一般来说,除了数据库创建后第一次启动以外,之后的每一次启动都会进行redo日志的应用。特别是当某些情况下数据库正常运行过程中发生了崩溃,断电,宕机等情况后,这些情况下数据库没有能力对已产生的事务进行处理,或者事务已经提交后,但数据还保留在内存中没有写到硬盘上的场景,这种状况下的事务被称为中间状态,意味着事务得到了部分处理,但没有完全完成。
试想,某些客户端数据库正在在对表里面的数据进行update操作,此时承载业务的数据库服务器发生了故障。此时数据库中的redo日志记录了某个客户端即将对某某表中的某个字段进行udate,而实际上此时update尚未完成,数据库已经处于离线状态了;这种情况下数据文件中的记录也无法体现数据库崩溃时的状态,只能通过数据库再次启动之后检查控制文件的内容进行确认(确认上一次数据库的关闭是否是正常关闭)。
Oracle的控制文件中记录了关于数据库的基本配置信息,如数据库实例名、表空间名、创建时间、联机(redo)日志状态/个数/组信息等外还包含数据库检查点、撤销段的开始与结束等信息。通常,数据库再次启动之后,需要检查控制文件中所记录的关于数据文件的SCN编号。
通过检查控制文件与联机日志中的检查点SCN信息,检查结果无非两种。倘若数据库实例启动后控制文件中记录的检查点SCN编号为 NULL,则表示数据库是正常关闭的,如果数据库处于正常运行状态,控制文件中的SCN编号则会始终保持为 NULL,当数据库正常关闭时,会进行数据库事务提交,并在数据库层面进行全局检查点,并更新检查点SCN的编号,这种情况下,数据库再次启动之后无需进行事务恢复的操作。
而另一种情况则发生在数据库运行过程中突然崩溃,显然这种场景下数据库无法进行事务提交或进行检查点,这就造成数据库控制文件中的SCN检查点的编号还是 NULL。当数据库实例再次启动时,根据控制文件获取到检查点SCN的编号信息为 NULL时,就会判断出实例上一次关闭为非正常关闭,这个情况下数据库则会根据联机日志等进行实例的故障恢复。
回滚
数据库回滚根据类型主要分为两类,分别是 前滚 和 后滚(RollBack)。
一般来说,数据库发生故障时退出的状态通常为非正常状态,该状态下数据库中仍旧存在部分事务提交了但其数据尚未写入到磁盘中的,或者一些未提交的事务仍存在于数据库中。
当数据库下一次启动时,会通过扫描控制文件中的检查点SCN进行确认,此时,一般根据事务状态的不同需要进行不同类型的事务回滚。
而 前滚 针对的事务类型则正是数据库异常关闭前已经提交的但尚未写入到硬盘中的数据。数据库通过查找控制文件中检查点的状态,从检查点的位置确认即将在数据库中将哪些事务进行重做。
所有的前滚事务应用完毕后,数据库中的buffer cache会恢复到实例崩溃时的状态,此时,数据库的buffer cache中既包含已经提交但没有写入硬盘中的脏数据(dirty data),也可能包含数据库崩溃时既没有提交也没有回滚的事务。
如下为一次数据库启动后事务前滚操作的日志记录
2022-03-16T15:07:08.236373+08:00
Completed redo scan (from RMS0's current RBA to end of thread)
read 4487538 KB redo, 1121072 data blocks need recovery
2022-03-16T15:07:12.000868+08:00
Started redo application at
Thread 2: logseq 5196, block 1234411, offset 0
2022-03-16T15:07:12.010245+08:00
Recovery of Online Redo Log: Thread 2 Group 16 Seq 5196 Reading mem 0
Mem# 0: xxxx/ONLINELOG/group_16.271.1095248029
...
2022-03-16T15:07:33.923822+08:00
Decreasing number of high priority LMS from 3 to 0
2022-03-16T15:07:44.997829+08:00
Completed redo application of 1135.59MB
2022-03-16T15:07:45.008590+08:00
Completed instance recovery at
Thread 2: RBA 5196.10258566.16, nab 10258566, scn 0x00000007d4b21273
1008417 data blocks read, 1962030 data blocks written, 4487538 redo k-bytes read
2022-03-16T15:07:45.028935+08:00
Thread 2 advanced to log sequence 5197 (thread recovery)
Instance recovery complete: valid 1 (flags x10, recovery domain flags xa0)
可以看到,数据库启动后oracle先对联机日志redo做了检查,并且确认了哪些数据块需要被前滚所恢复。前滚过程中被恢复的数据量为1135.59MB,前滚结束后,数据库记录了当前的检查点SCN编号为 scn 0x00000007d4b21273。
前滚 阶段完成后,数据库将会被打开。
但是此时数据库中仍然可能存在一些因为没有提交的、或没有回滚的脏数据,这些脏数据在数据库重新开始服务前也必须被回滚掉。前滚 阶段结束且数据库打开后,数据库会在后台进行事务回滚,这个阶段称为 后滚(RollBack)。
数据库在 后滚(RollBack) 阶段通过应用UNDO日志中的记录进行恢复,有些情况下,数据库进行undo事务的回滚可能会占用大量时间。
如下,为一次数据库实例启动时进行UNDO事务的后滚(RollBack)操作
2022-03-11T10:17:11.079539+08:00
Undo initialization recovery: Parallel FPTR complete: start:244648333 end:244648337 diff:4 ms (0.0 seconds)
Undo initialization recovery: err:0 start: 244648333 end: 244648338 diff: 5 ms (0.0 seconds)
2022-03-11T10:17:12.337267+08:00
[130979] Successfully onlined Undo Tablespace 2.
Undo initialization online undo segments: err:0 start: 244648338 end: 244649595 diff: 1257 ms (1.3 seconds)
Undo initialization finished serial:0 start:244648333 end:244649602 diff:1269 ms (1.3 seconds)
日志中可以看到oracle数据库打开后对undo中需要进行后滚(RollBack)的数据区块进行了确认,将 start:244648333 end:244648337 这个段的数据块进行了回滚,回滚后的事务区段为 start: 244648338 end: 244649595, 整个回滚过程消耗了1257ms。
最后
数据库的事务回滚是为了维护数据库一致性的必要性操作,前滚和后滚分别发生在数据库启动的不同阶段。数据库启动时,最先通过检查点SCN进行联机日志(REDO)事务的前滚,结束后数据库会通过SMON进程打开数据库,而后进行UNDO事务的回滚。