Oracle中的实例恢复与检查点学习笔记

时间:2022-07-01 07:43:40
1、实例恢复

1.1 事务的过程

假设用户启动了一个事务A,并使用某些新值更新某个表的一行,其服务器进程会将旧值复制到一个撤销段(data buffer),然后将变更写入

log buffer,最后会将变更应用到data buffer中的数据块。注意,这一过程没有涉及任何写磁盘操作。如果该过程实例崩溃,不需要任何恢复,

因为不存在提交事务。假设此时,存在另一个事务B,且B同样做了更改,并且未提交。

随后用户commit事务A,这时,LGWR会将log buffer中的更改写入磁盘上的online redo log(包括撤销段的更改和表的更改),确认写入后,

才会commit complete。此时,磁盘上的日志文件不仅包含了事务A已经提交的更改,也包含了事务B未提交的更改。

如果此时实例崩溃,那么可以根据online redo log恢复提交的事务,并回滚未提交的事务。

这样的话,即使已提交的事务未写入磁盘,或未提交的事务已写入磁盘,都能确保了已提交的事务不会丢失;未提交的事务不会保存。

1.2 实例恢复的过程

实例恢复包含两个阶段:

1.2.1 前滚阶段

前滚阶段负责恢复已提交的事务,在实例崩溃的那一刻,如果缓存中的数据未写入到磁盘中,会通过联机日志文件来重演数据变更。

1.2.2 回滚阶段

回滚阶段负责回滚未提交的事务,在实例崩溃的那一刻,如果未提交的事务已写入到磁盘中,同样会通过联机日志文件重构撤销段以回滚事务。

总之,LGWR会先于DBWn执行写操作,从而保证了事务可恢复。

1.3 DBWn和LGWR何时执行写操作

DBWn会在以下情况下,将脏数据块写入磁盘:

没有任何可用的缓冲区;

脏缓冲区过多;

三秒超时;

检查点。

LGWR会在以下情况下,将log buffer写入到磁盘:

COMMIT;

缓冲区占用达到1/3;

DBWn写入之前。

2、检查点

2.1 完全检查点

在Oracle8i之前,数据库的发生的检查点都是完全检查点。完全检查点会将数据缓冲区里面所有的脏数据块写入相应的数据文件中,同时将最新的checkpoint scn更新到所有的数据

文件头部及控制文件。保证数据库的处于一致的状态。需要注意的是,完 全检查点产生的时候,CKPT并不是把当前完全检查点发生那一时刻的SCN更新到控制文件和数据文件头,而

是将这个触发检查点时刻DBWn当前刚写完 dirty buffer对应的SCN更新到控制文件和数据文件头,也就是说,更新控制文件和数据文件头的SCN是滞后于完全检查点的发生那一时刻

的SCN的,这个从恢复的原理也很容易理解,因为检查点发生的时候要写入dirty buffer还没有写入,自然不能立即更新成当前的SCN了。需 要注意的是, 在oracle8之前,由于没有

chekpoint  queue,也没有增量检查点的概念,发生完全检查点时,DBWn会以一种无序的方式将所有的 dirty buffer写出到数据文件,这个时候Oracle会冻结所有DML操作等候所有

dirty  buffer被写出,巨大的IO往往会影响到数据库的性 能。后来随着Oracle数据库的发展和buffer  cache的不断增大,oracle 意识到这个单一的Full checkpoint机制已经不

能满足需要,所以在Oracle 8i后提出增量检查点的概念,建立了checkpoint queue ,让dirty  buffer  header根据首次变化时候的顺序(LRBA)排列在queue里面。 这样DBWn只要

顺着queue的顺序写,而其他进程不必等候dbwr的写完成就可以继续。  因此增量检查点的概念就由此产生了。

完全检查点在8i之后只有在下列两种情况下才会发生:

DBA手工执行alter system checkpoint的命令;

数据库正常shutdown (immediate,transcational,normal)。



2.2 增量检查点

增量检查点包括两方面的概念:

CKPT每3秒一次的检查DBWn写进度并在控制文件中记录检查点位置(checkpoint position)和更新heartbeat信息

以及

CKPT定期触发DBWn去写checkpoint queue中的脏数据

这两项操作合一起被称为增量检查点。

我们都知道被修改过的数据块,在oracle中都被统称为脏数据块(dirty buffer)。所有的脏块被一个链表串起来,称做检查点队列(checkpoint queue)。在buffer cache中,每一个块

都有一个buffer header简称BH, 在BH中有一个ckptq项, 此项目中记录了指向检查点队列上一个块和下一个块的指针。 如果某一个块不在检查点队列中,他的ckptq项为空.通过ckptq

项oracle将所有的脏块串成了一个双向链表。这个双向链表就是检查点队列了。

Oracle 从8i开始引入了检查点队列(checkpoint queue)的概念,用于记录数据库里面当前所有的dirty buffer的信息,这些dirty buffer的信息按被修改的时间先后存放在checkpoint

queue里面(当块首次被更改时,块会立即被加进检查点队列),所涉及的条目主要包含RBA (Redo Block Address,重做日志里面用于标识事务期间数据块在重做日志里面发生更改的编

号)和数据块的数据文件号和块号。

不论数据块(buffer)更改几次,它在checkpoint queue里面的位置始终保持不变,checkpoint queue也只会记录它最早的RBA(这个最早的RBA其实就是Low RBA,也就是数据块第一次被

修改时所对应的RBA),从而保证最早更改的数据块能够尽快从内存写入数据文件。DBWR每到一定的时机,就会被触发 (DBWn并不是只有当检查点发生的时候才写,它大约有10几种条件

触发写操作),沿着检查点队列的顺序刷新脏块,同时CKPT进程,会监控着检查点队列的长度,当检查点队列的长度达到一定限制时(具体有几个参数来确定checkpoing queue的长度,

下面会提到比如log_checkpoint_timeout,fast_start_mttr_target等),CKPT会通知 DBWR写脏块。CKPT会根据几个参数的设置和I/O的速度以及繁忙程度,计算出来一个Target rba

(目标rba),DBWn会沿着检查点队列,按照dirty buffer的Low RBA顺序将所有Target rba之前对应的脏块从内存写入数据磁盘文件。当CKPT通知完DBWn Target rba后,CKPT的任务就

结束了。他并不会等待DBWn写完所有的Target rba之前的脏块。因此这里CKPT只是起到了一个通知DBWn进程写入的作用。

当完全检查点发生的时候,Oracle一方面通知DBWn进行下一批写操作,另一方面是将这个触发检查点时刻DBWn当前刚写完dirty buffer对应的SCN写入数据文件头和控制文件,这个

SCN就是checkpoint scn。但Oracle考虑到检查点SCN的间隔还是太大了,因为检查点的触发条件有限,周期可能比较长,有些情况下比如检查点需要5分钟左右才触发,那这个时候

系统crash再重新启动就意味着很可能系统需要5分钟左右才能启动。于是Oracle采用了一个heartbeat的概念,以3秒的频率将DBWn写的进度反应到控制文件中,这样系统crash重新

启动的时候将从更近的一个时间点开始恢复。Oracle这么做的目的就是要缩短崩溃恢复时间! 因此CKPT另外一个任务就是每3秒,检测一次DBWn的写进度。DBWn 是沿着检查点队列写

脏块,由于这里有一个顺序的关系,所以DBWn的写的进度就是可衡量的,写到哪个buffer的时候该buffer的首次变化时候的 scn(对应了LRba)就是当前所有数据文件block的上面最新

scn,但是由于无法适时的将DBWn的进度记录下来,所以Oracle选择了一些策略。 其中就包括CKPT进程的检查点位置更新和心跳,所以说CKPT每3秒钟查看一下DBWn沿检查点队列写到

了哪里,并且将这个位置设置为检查点位置 (checkpont position)。也就是说检查点位置之前的块,都是已被DBWn刷新到磁盘上的块。因此我们可以理解为,CKPT进程每3秒会根据DBWn

写的进度设置并记录一个检查点位置,也就是说这个检查点位置就是由DBWn的在往Target RBA写过程中的进度决定的(如果没有dirty buffer产生,那么就不会更新检查点位置信息)。

因此CKPT每3秒会将检查点位置对应的数据块的rba (low cache rba-表示Instance Recovery时开始恢复的日志条目)更新和记录到控制文件的CHECKPOINT PROGRESS RECORDS区域,当

然同时被记录进控制文件的还有heartbeat等其他信息。DBWn将检查点队列里面的dirty buffer写入到数据文件后,检查点的位置也要相应地往后移。

检查点位置(checkpoint position)实际上就可以直接理解为是一个rba,他指向着重做日志文件中的某条重做记录。在此检查点位置前的重做记录,其对应的buffer cache中的

dirty buffer已经被写进了数据文件,在此位置后的重做记录,所对应数据脏块有可能还在内存中。如果发生了实例崩溃,只需要在日志文件中找到检查点位置 (low cache rba),

从此处开始应用所有的重做日志文件,就完成了前滚操作。实例崩溃后,再次启动数据库,oracle会到控制文件中读取low cache rba,这就是检查点位置。从此处开始应用重做日志,

应用到on disk rba的位置。on disk rba是磁盘中重做日志文件的最后一条重做记录的rba。如果某条重做记录的rba高于on disk rba,那说明此重做记录还没有被写进日志文件中,

崩溃发生时,他是不可能被恢复的。on disk rba是oracle前滚操作的终点。比这个更高的rba,都应该还驻留在log buffer中。还没有被LGWR写入日志文件。所以是不能被用于恢复的。

在DBWn写dirty buffer这个检查点过程中,Oracle也可以继续产生dirty buffer,DBWn也不是一次要把所有dirty buffer全部写到磁盘(不同于完全检查点的地方),这样就提高了检查

点的效率,使得数据库要做恢复的时候从这个最新位置开始做恢复,而不是从数据文件 中的checkpoint scn(上一个完全检查点位置) 开始做恢复,这样将缩短恢复时间。

Oracle 11g的 Concept里面提到:An incremental checkpoint is a type of thread checkpoint partly intended to avoid writing large numbers of blocks at online
redo log switches. DBWn checks at least every three seconds to determine whether it has work to do. When DBWn writes dirty buffers, it advances the checkpoint
position, causing CKPT to write the checkpoint position to the control file, but not to the data file headers.

因此我们需要注意的是:增量检查点并不会去更新数据文件头,以及控制文件中数据库SCN以及数据文件条目的SCN信息,而只是每3秒由CKPT进程去更新控制文件中的low cache rba

信息,也就是检查点的位置。

检查点位置发生变更后, Oracle主要通过4个参数和1个机制来控制检查点位置和最后的重做日志条目之间的距离(检查点队列的长度)。

fast_start_io_target (Oracle 9i以后已经废弃)

该 参数用于表示数据库发生Instance Recovery的时候需要产生的IO总数,它通过v$filestat的AVGIOTIM来估算的。比如我们一个数据库在发生Instance Crash后需要在10分钟内恢复

完毕,假定OS的IO每秒为500个,那么这个数据库发生Instance Recovery的时候大概将产生500*10*60=30,000次IO,也就是我们将可以把fast_start_io_target设置为 30000。

fast_start_mttr_target

我 们从上面可以看到fast_start_io_target来估算检查点位置比较麻烦。Oracle为了简化这个概念,从9i开始引入了 fast_start_mttr_target这么一个参数,用于表示数据库发生

Instance Recovery的时间,以秒为单位。这个参数我们从字面上也比较好理解,其中的mttr是mean time to recovery的简写,如上例中的情况我们可以将fast_start_mttr_target

设置为600。注意当设置了 fast_start_mttr_target后, fast_start_io_target这个参数将不再生效,从9i后 fast_start_io_target这个参数被Oracle废除了。

log_checkpoint_timeout

该参数用于表示检查点位置和重做日志文件末尾之间的时间间隔,以秒为单位,默认情况下是1800秒。相 比fast_start_mttr_target,它也是时间,但它的时间值表示完成恢复操作所

需要的时间,即从最后的检查点位置开始,应用所有日志直到 日志末尾所需要的时间。而本参数log_checkpoint_timeout表示从最后的检查点位置开始,到日志末尾经过的时间。

log_checkpoint_interval

该参数是表示检查点位置和重做日志末尾的重做日志块的数量,以OS块表示。

90% OF SMALLEST REDO LOG

除了以上4个初始化参数外,Oracle内部事实上还将重做日志文件末尾前面90%的位置设为检查点位置。在每个重做日志中,这么几个参数指定的位置可能不尽相同,Oracle将离日志文

件末尾最近的那个位置确认为检查点位置。


3、日志切换引发的检查点

我们可以通过以下演示证明日志切换引发的到底是何种检查点?:

SQL> select checkpoint_change# from v$datafile_header where status='ONLINE';
CHECKPOINT_CHANGE#
------------------
           1665476
           1665476
           1665476
           1665476
           1665476
           1665476
6 rows selected.

SQL> alter system checkpoint;
System altered.

SQL> select checkpoint_change# from v$datafile_header where status='ONLINE';
CHECKPOINT_CHANGE#

------------------
           1697131
           1697131
           1697131
           1697131
           1697131
           1697131
6 rows selected.

/* 手动执行checkpoint,数据文件头的checkpoint scn立即更新了 */

SQL> alter system flush buffer_cache;
System altered.

SQL> select checkpoint_change# from v$datafile_header where status='ONLINE';
CHECKPOINT_CHANGE#
------------------
           1697131
           1697131
           1697131
           1697131
           1697131
           1697131
6 rows selected.

/* 单纯flush buffer cache冲刷数据库高速缓存不会更新数据文件头的checkpoint scn */

SQL> alter system set log_checkpoints_to_alert=true;
System altered.

SQL> alter system set log_checkpoint_timeout=20;
System altered.

/* 设置log_checkpoint_timeout为20s,频繁引发增量检查点 */

alert log:
Wed Nov  3 20:24:49 2010
Incremental checkpoint up to RBA [0x3d.dff1.0], current log tail at RBA [0x3d.dff6.0]
Wed Nov  3 20:25:07 2010
Incremental checkpoint up to RBA [0x3d.dff7.0], current log tail at RBA [0x3d.dffc.0]
Wed Nov  3 20:25:25 2010
Incremental checkpoint up to RBA [0x3d.dffd.0], current log tail at RBA [0x3d.e002.0]
Wed Nov  3 20:25:43 2010
Incremental checkpoint up to RBA [0x3d.e003.0], current log tail at RBA [0x3d.e008.0]
Wed Nov  3 20:26:01 2010
Incremental checkpoint up to RBA [0x3d.e009.0], current log tail at RBA [0x3d.e00e.0]

SQL> set time on;

20:26:38 SQL> select checkpoint_change# from v$datafile_header where status='ONLINE';
CHECKPOINT_CHANGE#
------------------
           1697131
           1697131
           1697131
           1697131
           1697131
           1697131

6 rows selected.

/* 可以看到增量检查点并不会引起数据文件头的checkpoint scn 被更新 */

20:26:43 SQL>  alter system set log_checkpoint_timeout=1800;
System altered.

/* 那么日志文件切换就会引起数据文件头的checkpoint scn被更新吗?*/

20:28:10 SQL> alter system switch logfile;
System altered.

20:29:16 SQL> select checkpoint_change# from v$datafile_header where status='ONLINE';
CHECKPOINT_CHANGE#
------------------
           1697131
           1697131
           1697131
           1697131
           1697131
           1697131
6 rows selected.

/* logfile switch 日志文件切换引起的是一种slow慢的完全检查点,它不同于alter system checkpoint(ASC),
   ASC要求的脏块写出和控制文件及数据文件头更新时要立即完成的,也就是说当alter system checkpoint语句返回"System altered."
   后以上工作都已经完成了;而alter system switch logfile或者自然的日志切换引发的是一种慢的完全检查点,
   它在返回"System altered"时不要求写脏块等工作必须已经完成
*/

/* 我们可以用冲刷高速缓存的方式保证脏块写出的工作被督促完成 */

20:33:39 SQL> alter system flush buffer_cache;
System altered.

20:33:45 SQL> select checkpoint_change# from v$datafile_header where status='ONLINE';
CHECKPOINT_CHANGE#
------------------
           1697544
           1697544
           1697544
           1697544
           1697544
           1697544

6 rows selected.

/* 虽然日志切换所引发的slow checkpoint(慢的检查点)并无立即完成的要求,但也并非全无限制;
   当某次日志切换由1号日志组切换到2号日志组时,
   将引发一个slow checkpoint,之后日志连续切换又要切到1号日志组时要求之前的那个slow checkpoint在切换前必须完成
*/

20:41:35 SQL> set timing on;

20:42:02 SQL>  select * from v$log;
    GROUP#    THREAD#  SEQUENCE#      BYTES    MEMBERS ARC STATUS           FIRST_CHANGE# FIRST_TIME
---------- ---------- ---------- ---------- ---------- --- ---------------- ------------- -------------------
         1          1         67   52428800          2 YES INACTIVE               1698288 2010-11-03 20:41:19
         2          1         68   52428800          2 YES INACTIVE               1698292 2010-11-03 20:41:21
         3          1         69   52428800          2 NO  CURRENT                1698302 2010-11-03 20:41:35
Elapsed: 00:00:00.00

20:42:17 SQL> delete tv;
51134 rows deleted.
Elapsed: 00:00:01.68

20:42:34 SQL> commit;
Commit complete.
Elapsed: 00:00:00.00

20:42:36 SQL> alter system switch logfile;
System altered.
Elapsed: 00:00:00.01

20:42:40 SQL> alter system switch logfile;
System altered.
Elapsed: 00:00:00.01

20:42:43 SQL> alter system switch logfile;
System altered.
Elapsed: 00:00:02.00

20:45:28 SQL>  select checkpoint_change# from v$datafile_header where status='ONLINE';
CHECKPOINT_CHANGE#
------------------
           1700686
           1700686
           1700686
           1700686
           1700686
           1700686
6 rows selected.
Elapsed: 00:00:00.00

alter.log告警日志中的内容:

Wed Nov  3 20:42:40 2010
Beginning log switch checkpoint up to RBA [0x46.2.10], SCN: 1700686
...........................
Wed Nov  3 20:42:45 2010
Thread 1 cannot allocate new log, sequence 72
Checkpoint not complete
....................
Completed checkpoint up to RBA [0x46.2.10], SCN: 1700686

/* 最近一次的日志切换耗费2s,在告警日志中可以看到此次slow checkpoint的相关记录 */