Oracle事务基本原理 【转自大话RAC】

来源:互联网 发布:如何搭建p2p网络 编辑:程序博客网 时间:2024/05/23 12:01

Oracle事务基本原理


要想灵活地运用Oracle恢复技术,就需要了解Oracle对于事务的处理原理。接下来我们会通过图9-1详细讨论Oracle对于事务的内部处理机制。我们以一个语句为例,来了解Oracle的内部处理流程。

  1. SQL> insert into table my_table values(1);  
  2. SQL> commit;  
  3. Commit complete. 

用户进程连接到数据库,数据库会为这个用户进程创建一个服务器进程(也叫影子进程);这个进程就像用户进程的影子,替用户完成各种操作。

用户发出了第一个insert语句,向指定表中插入一条记录。

因为每条记录最终都要保存到一个数据块中,因此,Oracle会检查这个目标数据块是否已经存在于Data Buffer Cache中,如果不存在,影子进程就要负责把这个数据块从磁盘文件读取到SGA中。

影子进程就要修改这个数据块内容,把新的记录插进去;但是在这之前还需要构造Undo数据块,以备回滚;同时还要生成这两个操作的Redo记录,并把Redo记录放在Log Buffer Cache中。

第一个insert语句的操作完成;用户可以继续输入其他语句。

用户输入commit,要提交这个插入操作。

LGWR进程被触发,把Log Buffer中的日志记录到当前联机日志文件中。

一旦LGWR的写操作成功,用户就会收Commit complete的提示。用户就可以确认,本次修改已经被保存了,Oracle承诺所有Commit complete的事务不会丢失。

 图9-1  Oracle内部事务处理流程

到目前为止,用户的插入操作已经成功了,用户可以认为他的插入已经被记录到了数据文件中。但实际上我们还没有看到数据写(也就是DBWR进程)的活动。

Oracle在运行过程中,所有对于数据的修改都是在内存中进行的。Oracle每要修改一个记录必须先把记录所在的数据块加载到内存中,然后在内存中进行修改。但是提交(Commit)时,只是把Redo Log Buffer中的日志写到磁盘,修改的数据块不会立即写回磁盘。也就是说,Redo Log Buffer中的事务信息会被LGWR进程非常频繁地写到磁盘上,而修改的数据块只是被DBWR进程定期地写到磁盘上。

LGWR把Redo Log Buffer中的事务信息写到联机日志文件中的算法,和DBWR把Data Buffer Cache中的数据写到数据文件中的算法完全不同。这是因为这两个Buffer的目的本身就不一样。Log Buffer的目的是临时的缓存事务变化,然后尽快地把这些变化写到一个安全的地方去(联机日志文件);而Data Buffer Cache的目标是尽可能地把数据块放在内存中久一点、再久一点,这样可以改进那些频繁访问的数据块的性能。这么做是基于性能的考虑,Oraclce是采用"延迟写"的算法定期批量地把数据块写回磁盘。对于代表本次修改操作的Redo记录必须要先被保存下来(Write Ahead Logging)。

由于LGWR的活动和DBWR的活动不是同步的,因此,任意一个时间点上,那些已经提交的、被记录到联机日志文件中的事务,对应的数据文件中的数据可能是未提交的。同样,任何一个时刻,写到数据文件中的数据有可能是未提交的数据。这并不意外,Oracle会跟踪哪些事务提交与否,并确保数据的一致性读。当发生意外失败时,Oracle也能够判断事务提交与否,并根据Redo和Undo信息完成事务的回滚。

因此在数据库运行过程中,内存的内容总是比磁盘数据新。当数据库正常关闭时(SHUTDOWN IMMEDIATE、SHUTDOWN NORMAL、SHUTDOWN TRANSACTIONAL),Oracle会把SGA内容全部写回磁盘后才关闭数据库,这时内存和磁盘就完全同步了。所以正常关闭数据库后数据不会丢失。但是如果数据库是异常关闭(SHUTDOWN ABORT或者断电),内存数据来不及同步到磁盘,这时就产生了数据不一致,Oracle再次打开数据库时,就需要进行恢复。

Oracle的Redo机制保证了数据库恢复的可行性,在修改数据块之前,代表本次修改操作的Redo记录必须要先被保存下来(Write Ahead Logging),然后才真正修改数据记录。在处理COMMIT语句时,Oracle会在Log Biffer产生一条COMMIT记录,为了保证事务的持久化,所有的Redo记录和这一条COMMIT记录都要被写到磁盘的联机日志文件(Log Force At Commit),但是数据块(Data Block)不必写回磁盘。如果当前联机日志空间不够,还会触发日志切换(Log Switch),旧日志的检查点必须完成才能被覆盖。如果采用的是归档模式,这个日志还必须完成了归档才能被覆盖。

9.1.1  SCN

只要是谈到事务、恢复,就离不开SCN。SCN号是一个重要的数据结构,它相当于Oracle内部的时钟机制,读者可以把它想象成逻辑时钟。每个事务都会被赋予一个SCN值,因此它表明了某个确切时刻的数据版本,事务的SCN号是按照事务的提交时间递增的,但不一定连续。Oracle按照SCN对日志内容进行排序,就可以得到操作历史,Oracle也是根据SCN来判断数据文件是否需要恢复的。SCN会出现在事务表、数据块头、控制文件、数据文件头以及Redo记录中。表9-1总结了几个重要的SCN。

表9-1 Oracle中一些重要的SCN

 

System
checkpoint

SCN

Datafile
checkpoint

SCN

Start

SCN

End SCN

存放

位置

控制

文件

Ö

Ö

 

Ö

数据

文件头

 

 

Ö

 

查看方法

Select
checkpoint_

change# from

 v$database

select checkpoint_

change# from

v$datafile;

select 
checkpoint_

change

# from 
v$datafile

_header

Select
name, last_

change#

from v$datafile

说明

信息来自于
控制文件

v$datafile上部分列

的信息
来自于控制

文件

v$datafile_
header

的信息来

自于每个

数据文件

的文件头

v$datafile上部分列

信息来

在于
控制文件

1.系统检查点SCN

这个检查点是系统范围的,每当一个检查点动作完成,Oracle就把这个检查点对应的SCN记录到控制文件中,这个检查点可以用下面方法获得。

  1. ZXMDB> select checkpoint_change# from v$database; 

使用下面命令触发检查点,观察其变化。

  1. ZXMDB> alter system checkpoint; 

2.数据文件头SCN

每个数据文件头也包含检查点结构,当进行文件检查点或者实例检查点、全局检查点时,所有的数据文件文件头的检查点结构也会更新。唯一的例外就是采用User-Managed方式的热备份时,这是数据文件头的检查点SCN将被锁定,直到发出对应的end backup命令为止。

  1. ZXMDB> select checkpoint_change# from v$datafile_header; 

保存在数据文件头的这个SCN也叫作数据文件的启动SCN。

3.数据文件的检查点和终止SCN

控制文件中也会记录每个数据文件的检查点和终止SCN。到目前为止,数据文件已经有3个SCN了,不过这3个SCN的位置不一样,启动SCN记录在数据文件头中,而检查点SCN和终止SCN记录在控制文件中。这3个SCN用来确认数据文件是否需要恢复。

查看终止SCN:

  1. ZXMDB> select name,last_change# from v$datafile; 

4.几个SCN的关系

在数据库正常运行过程中,每个数据文件的终止SCN会被设置为无穷大(NULL),而其他的那些SCN应该完全一样。

如果数据库是正常方式关闭(SHUTDOWN IMMEDIATE、SHUTDOWN NORMAL、SHUTDOWN TRANSACTIONAL),关闭之前数据库会执行一个检查点动作,每个数据文件的终止SCN会被设置成启动SCN。而如果数据库异常关闭(如SHUTDOWN ABORT、断电),终止SCN来不及设置为启动SCN,仍然保持NULL。

数据库再次启动时,会比较这些SCN是否一致,如果不一致则说明要进行恢复。下面例子演示了数据文件启动SCN和终止SCN的变化。

(1)数据库运行过程中,检查每个数据文件,所有数据文件的终止SCN都是NULL。

  1. ZXMDB> select checkpoint_change#,last_change# from v$datafile;  
  2.  
  3. CHECKPOINT_CHANGE# LAST_CHANGE#  
  4. ------------------ ------------  
  5.            1143438  
  6.            1143438  
  7.            1143438  
  8.            1145816  
  9.            1143438 

(2)正常关闭数据库后,每个数据文件的终止SCN会被设置成启动SCN。可以通过把数据库启动到MOUNT状态来观察这个值。

  1. ZXMDB> shutdown immediate;  
  2. ZXMDB> startup mount;  
  3. ZXMDB> select checkpoint_change#,last_change# from v$datafile;  
  4.  
  5. CHECKPOINT_CHANGE# LAST_CHANGE#  
  6. ------------------ ------------  
  7.            1146056      1146056  
  8.            1146056      1146056  
  9.            1146056      1146056  
  10.            1146056      1146056  
  11.            1146056      1146056 

(3)异常关闭数据库时,终止SCN来不及被修改,再把数据库启动到MOUNT状态,Oracle就知道这些数据文件需要恢复。

  1. ZXMDB> shutdown abort;  
  2. ZXMDB> startup mount;  
  3. ZXMDB> select checkpoint_change#,last_change# from v$datafile;  
  4.  
  5. CHECKPOINT_CHANGE# LAST_CHANGE#  
  6. ------------------ ------------  
  7.            1146130  
  8.            1146130  
  9.            1146130  
  10.            1146130 

5.日志文件的SCN

每个日志文件都有一个序列号用来在日志历史中唯一的标识该文件。每个日志文件会有一个低SCN和高SCN。低SCN代表者这个日志文件记录的第一个Redo记录的SCN,高SCN代表这个文件记录的最后一个Redo记录的SCN。当一个日志文件存满Redo记录后,就会被关闭并打开一个新的日志文件。这个新的日志文件的低SCN就用上一个日志文件的高SCN加1来表示。每个正被使用的日志文件的高SCN是无穷大,因为Oracle还不知道这个日志将会记录多少个SCN。通过查询V$LOG_HISTORY视图可以获得这个信息:

  1. select thread#,sequence#,first_change#,next_change# from v$log_history ; 

每个日志的FIRST_CHANGE#和NEXT_CHANGE#就是低SCN和高SCN。

9.1.2  检查点

检查点是一个数据库事件,它的功能是触发DBWR进程把Data Buffer中的脏数据写入数据文件,然后CKPT进程更新控制文件和数据文件头。检查点的意义在于,它描述的是数据库的一致性状态。在检查点执行完毕那一个时间点,数据库是处于一致状态的,也就是说Data Buffer中内容和数据文件中的内容完全一致,所有的数据修改都已经被保存到了数据文件中(无论这个修改是否被提交)。在这一时刻,所有Redo内容对于实例恢复不再有效了。

检查点可以分成以下几种类型。

局部检查点和全局检查点。

局部检查点又叫作实例检查点或者线程检查点,是由一个实例触发的对所有数据文件的一个检查点动作。全局检查点又叫作数据库检查点,是所有的实例对数据文件的检查点动作。这种区别在RAC环境中才有意义,对于单实例没有意义。对应的语法是:

  1. alter sysem checkpoint local/global; 

文件检查点。

所有数据库实例同时对一个数据文件执行一个检查点动作,这种检查点动作见于User-Managed Backup,当利用"alter tablesapce … begin backup"命令把表空间设置为备份模式时,会触发这种检查点。或者把表空间设置为只读状态、脱机状态时,也会触发这种检查点。

无论是哪一种检查点,区别不过是触发的方式不同而已,而在Oracle内部,处理机制都一样,也就是检查点队列数据结构。

检查点队列。

Data Buffer中的脏数据块以队列这种数据结构链接起来,这个队列就叫作检查点队列(Checkpoint Queue)。脏数据块在这个队列中是按照数据块变脏的最早时间顺序排列的,实际就是按照每个数据库生成的第一个的Redo记录时间排序的。比如,最初数据块1被修改,被放在链表的表头为止,接着数据块2、数据块3……被修改,这些数据块按顺序接到链表上,再后来,数据块1又被修改,但是在这个链表上数据块1的位置是不能移动的。换句话说,一旦脏数据块被放在检查点队列中 ,它就停留在同一位置,直到将它写出为止。检查点队列示意如图9-2所示。

 图9-2  Checkpoint Queue

DBWR进程在响应检查点请求时,就按照检查点队列从头到尾的顺序写出脏数据块。每个检查点都会有一个SCN值,一旦DBWR写出的数据块的Redo记录等于或者大于检查点的SCN,检查点动作就宣告结束,同时更新控制文件和数据文件头。因为检查点队列是按照最早Redo记录的时间排序的,并且DBWR进程也是按照这个顺序写出脏数据块,如果同时提交多个检查点请求,多个检查点请求就可以合并处理,只要某个检查点的SCN小于被写出的SCN,这个检查点就可以被宣告结束,DBWR继续写出,直到所有检查点都结束。

每当DBWn进程把一个数据块写到磁盘文件后,会生成一条BWR(Block Write Record)记录,这个记录代表着在这个时刻数据块是同步的,即磁盘上的内容和Buffer Cache中的内容一致,BWR记录在恢复阶段会用到。图9-3说明了BWR记录的生成。

 图9-3  BWR记录

知道那些文件需要恢复后,下一步就可以通过重演日志,把数据文件恢复到一致状态。

相对备份而言,恢复操作比较复杂。恢复可以分成实例恢复(Instance Recovery)、介质恢复(Media Recovery),后者又可以分成完全恢复(Complete Recovery)和不完全恢复(Incomplete Recovery)。要想全面掌握恢复,就必须对Oracle的联机日志、归档日志有全面透彻的了解,所以下面先回顾一下这两种日志。

9.2  日志

Oracle数据库的日志包括联机日志和归档日志。所有的Oracle数据库都需要有联机日志,而且至少要有两组联机日志。而归档日志只有使用归档模式时才会需要,生产数据库必须运行在归档模式。联机日志循环使用,当一组联机日志写满后,就要切换到另一组联机日志,后者的内容就被覆盖,这个过程叫作日志切换(Log Switch),在日志切换时会触发检查点(Checkpoint),如果数据库是归档模式,还必须完成日志的归档操作。

9.2.1  日志内容

Redo日志中记录了数据库的改动情况,下面就来看Oracle使用哪些数据结构来记录改动的。

1.改动向量(Change Vector)

改动向量描述对数据库中任何一个数据块所做的一次改动。具体由3个内容组成:

数据本身的改变;

对应的UNDO的改变;

Undo Segment的改变。

比如,要修改Employee表中某个用户的工资,那么修改之前的数据就保存在UNDO中,为了申请事务,还需要修改Undo Segment段头事务表的信息,这3个就构成了一个Change Vector。

改动向量中的信息包含版本号、事务操作代码以及改动的数据块地址。在建立向量时,会从数据块中复制版本号,在恢复期间,Oracle会读取向量并将改动应用于对应的数据块,同时对数据块的版本号加1。

2.Redo记录

Redo记录由一组改动向量组成,用于描述对于数据库的一个"原子"改动(或者说一个事务)。比如一个最简单的Update操作可能涉及多个数据块内容的改变,但无论涉及多少个数据块,这些改动从逻辑上都要被当作一个整体看待。也就是说,这个Redo记录中的所有改动向量必须全部成功,否则就全部回滚。有些事务可能会生成多个Redo记录,每个Redo记录都会有一个改动向量集。无论发生何种系统故障,恢复都必须保证Redo记录中的全部改动向量被全部应用或者全不应用。

下面用一个例子说明改动向量和Redo记录的创建过程,初始情况如下:

磁盘数据ID = 5,SAL = 12345;

用户执行"Update .. set sal = 23456 where id = 5;";

Server Process从磁盘把数据读入Buffer Cache;

要开始这个事务,首先需要申请一个UNDO资源,也就是获得一个UNDO数据块来存放数据的前镜像,假设这个UNDO Block是UB1,这个UNDO数据块会被读入到Buffer Cache;如图9-4所示,左边的方框代表Data Block,右边的方框代表UNDO Block。

在开始修改数据之前,数据的前镜像被保存到UNDO中,因此UNDO记录的内容就是SAL = 12345;这是第一个改动向量,即对UNDO数据块的改动。

对于UNDO Block的修改会生成一条Redo记录,Redo记录的是发生的变化,也就是UNDO Block被修改后的状态;注意Redo记录的内容包括了被修改的数据块的位置、事务号、修改后的值,如图9-5所示。

 图9-4  事务初始(读如数据,申请UNDO) 图9-5  UNDO记录的生成也导致Redo记录的生成

UNDO记录生成后,Oracle就可以修改数据了,记录被修改成SAL=23456;这个改动是对数据块的改动,也会生成一个改动向量;

同时生成一条Redo记录,用来反映这种变化;注意Redo记录的事务号和上一条相同,同时Redo记录了被修改数据的位置信息、修改后的值,如图9-6所示。

 图9-6  对数据的修改生成一条Redo记录

到目前为止,这个事务包含了两个改动向量:对于UNDO数据块的改变和对数据块的改变。但真实情况可能还不止于此,比如改动内容如果涉及索引列,则索引键也需要修改,就会生成第三个Redo记录,更多的改动向量。同样如果事务之后发出了COMMIT语句,也会创建一个Redo记录。因此,由于事务是一个原子的变化单位,在恢复时所有这些改动向量都应该被应用,或者一个也不应用,这才能保证数据库的一致性。

继续其他事务,下面这些过程会在下一节中用到。

另一个用户执行了"Update … set name='zhang' where name='Huang'"语句,处理流程同样,如图9-7所示。

用户1执行Commit,这个命令会生成一个Commit记录,代表这个事务已经结束,结束的时间以及SCN。

Commit动作会触发LGWR,后者把Redo Buffer中的内容写到Redo Log File中,注意没有提交的事务的Redo记录也会被写到日志文件中,如图9-8所示。

 图9-7  其他事务 图9-8  Commit触发LGWR写

9.2.2  日志线程(Redo Thread)

这个名词对于初次接触RAC的读者比较陌生,但实际并不是什么新技术,单实例环境下也有Redo Thread,只是很少被提及。

每个实例用到的联机日志就是一个Redo Thread,单实例有且只有一个Redo Thread。在RAC环境下,每个实例都需要自己的联机日志,也就是每个实例都有自己的Redo Thread。这种每实例一个Redo Thread的设计是为了避免实例间共享Redo文件引发的竞争,提高系统性能。

每个日志线程都有一个状态,数据库启动过程中会根据这个状态判断是否要恢复,后面的章节将会详细介绍。RAC数据库通过thread这个参数用来指定实例使用的Redo Thread线程号,一般和该实例的INSTANCE_NUMBER参数相同。通过视图V$LOG的THREAD#列可以确认日志组所属的线程。

因为RAC环境下有多个日志线程,所以在添加日志时必须指定线程号。

  1. SQL> alter database add logfile thread 1group 5 ('/oracle/oradata/redo5') size 50M;

9.2.3  日志写

在数据库运行过程中,所有的DML、DDL操作都被按顺序记录到联机日志中,每个记录叫作一个Redo Log Record,每个Redo Log Record中会记录当时的SCN号,可以把SCN想象是一个时间戳,这样在恢复时,就可以按照SCN排序把这些Redo Log Record重新执行,相当于数据库操作历史的重演。

每个Redo Thread(或者Instance)需要至少两个日志组,这些组循环使用。当一个联机日志写满后,会发生Log Switch使用下一个联机日志组。如果数据库是归档模式,该联机日志还要被归档。在Log Switch时,会触发Checkpoint,Checkpoint会启动DBWR后台进程把Data Buffer Cache中Dirty Block写入磁盘。更准确地说,是把该日志中的所有操作涉及数据块写到磁盘。一旦Checkpoint完成,就意味着该联机日志所覆盖的操作都被同步到数据文件了,这时这个联机日志就可以被重用了。

但是,读者必须知道DBWR的写操作是"分散写",也就是说,它要写的Dirty Block在磁盘上不是连续分布的,在执行写的过程中磁盘磁头要不断地跳来跳去寻址。这样DBWR就需要很长时间才能完成Dirty Block的写操作。如果Logfile Switch必须等待DBWR写完(这是Oracle 8之前Checkpoint的算法),就会导致用户进程必须长时间等待。

从版本8开始,Oracle使用了新的Incremental Checkpoint算法。执行检查点时,只是在控制文件中记录当时的Checkpoint SCN,然后DBWR进程就在后台进行写操作,每隔3秒钟,DBWR会在控制文件中更新Checkpoint Progress Record,代表工作进展情况。而用户进程继续前台操作,不受DBWR的影响。

在Incremental Checkpoint算法下,如果是非常繁忙的系统,就有可能出现这种情况:当前使用的是3号联机日志组,1号联机日志组对应的DBWR操作完成,但是2号联机日志组对应的DBWR操作还没有完成。这时1号联机日志组的状态是INACTIVE;2号联机日志组的状态是ACTIVE的;而3号联机日志组的状态是CURRENT。这些状态可以从V$LOG视图的STATUS列查看。无论是ACTIVE状态的联机日志(DBWR写操作没有完成)、还是CURRENT状态的联机日志,都说明其涵盖的修改没有被同步到数据文件。因此,在进行恢复时,这两个日志都需要。区别在于,如果是ACTIVE的联机日志丢失,但是该日志对应的归档日志存在,则通过使用归档日志进行恢复,该日志涵盖的修改不会丢失;如果CURRENT的联机日志丢失,则修改就真的丢失了。

前面提到的THREAD、STATUS都可以从V$LOG视图中查看:

  1. SQL> select group#,thread#,archived,status from v$log; 
在RAC环境下,用户操作是分布在多个实例间,各实例有自己的联机日志。恢复时必须把所有实例的联机日志都合并,把Redo Log Record按照SCN排序,才能整理出准确的用户操作历史。所以RAC的联机日志必须放在共享存储上,保证实例都能访问其他实例的联机日志。

注释:

在RAC环境下,每个实例都需要自己的联机日志,也就是每个实例都有自己的Redo Thread。

RAC的联机日志必须放在共享存储上,保证实例都能访问其他实例的联机日志。


在RAC环境下,除了日志每个实例各有一份,还有哪些文件如此?

共享控制文件 RAC  谷歌


0 0
原创粉丝点击