上一篇文章给大家梳理了一条更新语句在Oracle数据库中是如何执行的,我们也提到只要更新记录成功写入到在线重做日志文件,Oracle就能保证数据不会丢失。同时也向大家解释了,其实这个时候数据并没有写入到数据文件,因此这个时候仍然是一个不一致的状态。那么Oracle是如何做到写入的数据不会丢失呢?数据库接下来还会有哪些后续的操作?
两个重要概念
这节内容开始,我们还是给大家介绍两个重要的概念:SCN 和检查点事件。没办法,Oracle的概念实在太多,为了便于大家的理解,我们会在章节开始之前尽量把相关的概念交待清楚。
SCN
SCN的全称是 System Change Number,是一个单调递增的全局逻辑时钟,用于标识事务、跟踪数据库中的所有变更,对于维护多版本下的数据一致性起着非常重要的作用。所有支持事务的数据库基本上都有SCN的概念,不同数据库中的名称和实现上会有所差别。
Oracle SCN是一个6字节(48位)的数字,每提交一个事务时,都会为其分配一个全局唯一的SCN。在Oracle数据库中,SCN和时间戳是一一对应的,每个SCN都可以和时间戳进行换算。不论是在线日志文件、数据文件,还是控制文件,写出的时候都会带上SCN,通过SCN可以关联出一个全局一致性的状态。
以下是一段引用自官方《Concept》文档中的描述:
A system change number (SCN) is a logical, internal timestamp used by Oracle Database. SCNs order events that occur within the database, which is necessary to satisfy the ACID properties of a transaction. Oracle Database uses SCNs to mark the SCN before which all changes are known to be on disk so that recovery avoids applying unnecessary redo. The database also uses SCNs to mark the point at which no redo exists for a set of data so that recovery can stop.
数据库检查点事件
不知道大家是否还记得,前一篇文章中我们提到在线日志是同步写出的,数据块则是异步写出的。Redo Log写出到日志文件后,Buffer Cache中的数据并没有写出到数据文件,此时数据库还是处于一个不一致的状态。那什么时候才能达到一致呢,答案就是检查点发生之后。
检查点的主要功能包括:
- 确保内存中修改过的数据块定期写入磁盘,以便在系统或数据库发生故障时数据不会丢失;
- 定期进行检查点操作,减少故障发生时需要恢复的数据,降低恢复时间。
在数据库缓冲区中,被修改的数据块被称为“脏块”,所有的脏块会按照操作时间的先后顺序(确切的说是SCN - System Change Number)被放入到一个独立的链表,这个链表构成了需要写出的数据块队列。当检查点发生时,Oracle依次从检查点队列中取出数据,由数据库DBWR进程写出到数据文件中。在Oracle数据库中有一个核心进程专门负责检查点事件的操作,其主要的功能是在检查点事件发生时,将检查点信息更新到数据文件头部和控制文件中,完成一次完整的检查点事件,这时才算是达到了一致的状态。
但是这个时候真的是一致性的状态吗?数据库是 24 * 7 持续对外服务的,不断的会有新的数据被更新,根本不会有绝对的一致性状态。那么Oracle是怎么保证各种异常情况下数据不会丢失呢?
异常恢复的几种场景
数据库是个软件系统,运行在由服务器、网络设备等构成的硬件系统和由操作系统、数据库软件等构建的软件系统之上,每个层面异常都有可能会导致异常的发生。不同的异常对数据造成影响程度会有所差别。
接下来我们讨论常见的数据库异常场景下,Oracle是如何保障数据不丢失的。
实例恢复
实例崩溃是非常典型的数据库异常,服务器异常重启、内存损坏、操作系统和数据库自身的Bug等都可能会导致数据库实例崩溃。实例崩溃通常是没有任何征兆的,数据库系统来不及做任何反应就崩溃了,因此只有在下次重启时进行实例恢复。
如上图所示,最后一次检查点之前的数据已经提交并且被更新到数据文件,其状态是一致性,实例恢复过程中不需要做任何操作,这里我们不做过多的讨论。因此,实例恢复的起始位置是最后一次检查点的位置,实例恢复的终点是在线重做日志文件的最末端,这之间的数据包括两种状态:
- 已更新但是还没有提交的数据,对于这部分数据并不需要保证其数据完整性,因此Oracle会调用UNDO中的“前镜像”数据进行回滚,恢复到修改之前的状态;
- 已更新且已完成提交、但“脏块”还没来得及写出到数据文件的数据,对于这部分数据,Oracle会读取在线重做日志中的数据,将最新的数据内容应用到数据文件。
简单来说,未提交的数据回滚,已提交的数据前滚,Oracle通过这种方式将数据拉齐到一致性的状态后,即可完成实例恢复,之后打开数据库对外提供服务。
如图,假设崩溃瞬间的数据库数据文件、在线日志文件和控制文件的状态如下:
- 在线日志文件最大的SCN为143,最小的SCN为74;
- 控制文件中记录的SCN和在线日志文件最大SCN一致,为143;
- 数据文件最大SCN为140,最小SCN为99。
此时进行实例恢复,需要应用的日志范围则是从99至143。
实例恢复的动作是由SMON进程来完成的,实际案例中,经常会遇到由于UNDO、REDO等文件或数据块的损坏,无法构造一致性导致实例恢复失败,数据库无法正常打开的情况。这个时候需要采取其他手段来强制打开,通常这种手段都会破坏数据的完整性,只有万不得已的情况才会考虑。
介质恢复
在计算机环境中,我们经常把涉及到文件的对象称为“介质”。所谓介质恢复,是由于数据文件损坏或丢失而导致需要进行的恢复,比如存储损坏导致的数据文件丢失、人为误删除了某个数据文件等。根据损坏文件类型的不同,采取的技术手段也不同。
数据文件的恢复
数据文件是保存数据的最终所在,如果数据文件丢失或损坏,轻则访问其所保存的数据会报错,如果丢失的是SYSTEM和UNDO等系统数据文件,还会导致数据库崩溃而永远无法正常启动。
数据文件的恢复通常包括恢复(restore)和修复(recover)两个阶段。恢复阶段将相关文件从之前的备份中恢复出来,备份文件是过去某个时间产生的,和最新的数据状态存在一定的差距。因此需要使用归档日志和在线日志来弥补历史备份数据和最新数据之间的GAP,将数据从历史备份中恢复到最新状态,这是修复阶段所做的操作。
在线日志文件的恢复
在上一篇文章中我们也提到,在线日志文件是Oracle数据库的“软肋”,而且由于更新非常频繁,CURRENT状态的日志文件是不会备份的,只有写满切换到下一个日志文件之后,之前的文件才会被备份为归档日志文件。但如果CURRENT状态的日志文件出现丢失和损坏,会导致数据库实例的崩溃,而且无法正常完成实例恢复,这时常规的方法只能利用历史备份对数据库进行不完全恢复,根据日志的损坏情况,会造成一定程度的数据丢失。
为了避免在线日志成为“软肋”,通常我们建议在生产环境中为每组在线日志添加两个成员,理想状态是将两个成员放到不同的存储或不同的磁盘驱动器的不同文件系统中,避免存储等硬件故障或人为误操作导致在线日志文件的错误。对于两个成员的磁盘组,其中一个成员损坏不会影响数据库的正常运行,可以手工删除损坏的日志成员,添加一个新成员,完成在线日志文件的修复。
控制文件的恢复
Oracle的控制文件是一个二进制文件,记录了数据库的结构信息,包含数据文件、在线日志文件的位置和大小,以及归档日志、数据库备份等信息,是Oracle数据库体系中非常重要的文件。
和在线日志文件类似,控制文件由于更新频繁,也无法对其进行实时备份,所以在实际生产环境中也同样建议设置2到3个控制文件,分别放置到不同的存储或不同的磁盘驱动器的不同文件系统中。当其中一个备份文件出现错误时,可以从正常的控制文件拷贝出新的文件。不过,数据库运行过程中控制文件始终处于工作状态,因此拷贝操作需要将数据库完全关闭才能进行,这会涉及到一定的生产停机时间,修复成本相对更高。
总结
本篇文章给大家介绍了Oracle数据一致性的保障体系,在这个体系中,检查点是非常重要的环节,它定期将“脏数据"从内存写出到数据文件。由于数据库是持续运行的,没有绝对的一致性状态,因此检查点队列被设计成一个先进先出的链表,依次从检查点对列写出到数据文件中,完成数据的持久化。对于已经进入检查点队列但没有写出到数据文件的脏块,实例恢复时会通过在线日志前滚将已修改的数据恢复出来应用到数据文件中,从而保证数据的一致性。
最后我们也提到在线日志文件和控制文件是Oracle的“软肋”,建议通过多成员的方式来弥补架构上的单点。大家知道Oracle还有哪些单点的隐患吗,又该如何规避?