在Oracle中,什么是检查点?如何调优检查点?

♣答案部分



(一)什么是检查点?

在Oracle数据库系统中,写日志和写数据文件是数据库中消耗I/O较大的两种操作。在这两种操作中,写数据文件属于分散写,写日志文件是顺序写,因此为了保证数据库的性能和数据的安全,通常数据库都是在提交(COMMIT)完成之前要先保证Redo日志条目都被写入到日志文件中,才会给用户反馈提交完成的通知(Commit complete.),而保存在Buffer Cache中的脏块会不定期地、分批地写入到数据文件中。也就是说,日志写入和提交操作是同步的,而数据写入和提交操作是不同步的,修改的数据并不是在用户提交后就立马写入数据文件中。这样就存在一个问题,当数据库崩溃的时候并不能保证Buffer Cache里面的脏数据全部写入到数据文件中,那么在实例启动的时候就要使用日志文件进行恢复操作,将数据库恢复到崩溃之前的状态,从而保证数据的一致性。那怎么确定该从何时、从哪里开始恢复呢,Oracle使用了检查点(Checkpoint)来进行确定。

一般所说的检查点是一种将内存中的已修改数据块与磁盘上的数据文件进行同步的数据库事件(Event),是Oracle在数据库一致性关闭、实例恢复和Oracle基本操作不可缺少的机制。Oracle通过检查点确保被事务修改过的数据可以被同步至磁盘。检查点信息(Checkpoint Information)包含检查点位置(Checkpoint Position)、SCN、联机Redo日志中开始恢复的位置等。当检查点发生时,CKPT通知DBWn进程将脏块(Dirty Buffer)写出到数据文件上,并更新数据文件头及控制文件上的检查点信息。注意,CKPT进程不负责Buffer Cache中的脏数据写入到磁盘中,该工作由DBWn负责;CKPT进程也不负责将Redo Log Buffer中的数据写入到联机Redo日志文件中,该工作由LGWR负责。由于Oracle事务在提交的时候不会将已修改数据块同步写入磁盘上,所以,CKPT进程负责更新控制文件和数据文件头的检查点信息和触发DBWn写脏数据到磁盘。CKPT执行越频繁,DBWn写出就越频繁。

检查点位置(Checkpoint Position)是一种数据结构,在Redo日志流中记录的SCN号是在进行数据库实例恢复时的起始位置。检查点位置由在数据缓冲池中存在的最老的脏数据位置决定,并且检查点的信息存储在控制文件和数据文件头中。控制文件中记录的检查点位置是实例恢复的起点。在检查点位置前的Redo记录,其对应的Buffer Cache中的Dirty Buffer已经被写进了数据文件,在此位置后的Redo记录,所对应数据脏块有可能还在内存中。如果发生了实例崩溃,只需要在日志文件中找到检查点位置(Low Cache RBA),从此处开始应用所有的Redo日志文件,就完成了前滚操作。

(二)检查点的作用

检查点的主要目的是以对数据库的日常操作影响最小的方式刷新脏块,检查点主要有两个作用:

① 确保数据库的一致性,包含两个方面,第一,确保Buffer Cache中的Dirty Buffer能有规律地定期写入磁盘,这样在系统或数据库出现故障时就不会丢失数据;第二,确保数据库在一致性关闭期间可以将所有已提交了的数据写入磁盘。

② 实现更快的数据库恢复,主要是缩短实例恢复的时间。实例恢复要把实例异常关闭前没有写出到硬盘的脏数据通过日志进行恢复,只需要重新应用控制文件中记录的检查点位置之后的联机Redo日志条目即可进行恢复。如果脏块过多,那么实例恢复的时间也会很长,所以,检查点的发生可以减少脏块的数量,从而提高实例恢复的时间。

(三)检查点的分类

CKPT进程负责将检查点信息写入到数据文件头中和控制文件中,包括以下几种类型的检查点:

分类

简介

发生时机

完全检查点(FULL Checkpoint)

线程检查点(Thread Checkpoint)、数据库检查点(Database Checkpoint)

数据库将所有在数据缓冲区内由Redo修改过的数据写入到磁盘中,这个线程检查点在所有的实例中的集合称之为数据库检查点(Database Checkpoint)。

1.数据库一致性关闭的时候(IMMEDIATE、TRANSACTION、NORMAL或选项SHUTDOWN)。

2.执行ALTER SYSTEM CHECKPOINT语句的时候。

3.在线日志切换的时候(ALTER SYSTEM SWITCH LOGFILE)。

4.执行ALTER DATABASE BEGIN BACKUP语句的时候。

表空间和数据文件检查点(Tablespace And Data File Checkpoin

表空间的检查点是数据文件检查点的集合,每个数据文件都在这个表空间之内。将执行属于该表空间的所有数据文件的一个检查点操作。

1.将一个表空间设置为只读的方式。

2.将一个表空间设置为OFFLINE。

3.数据文件大小变化的时候。

4.执行ALTER TABLESPACE xxx BEGIN BACKUP的时候。

增量检查点(Incremental Checkpoint)

增量检查点是线程检查点的一种,是为了避免联机Redo日志切换的时候需要写入大量的脏数据到磁盘中。DBWn至少每3秒检查一次看是否有数据是否要写入到磁盘当中,当DBWn进程将脏数据写入到磁盘中时,从而推进了检查点的位置,会导致CKPT进程将检查点位置信息写入到控制文件(Control File)中,但是不会写入到数据文件头(Data File Headers)中。

1.恢复需求设定(FAST_START_MTTR_TARGET)。根据参数FAST_START_MTTR_TARGET的设置来确定。如果当内存中产生的Dirty Buffer所需的恢复时间(estimated_mttr)到达FAST_START_MTTR_TARGET的指定时间,那么检查点进程被触发。检查点进程一旦被触发,将通知DBWn进程将按检查点队列顺序将脏数据写入到数据文件,从而缩短了最后检查点位置与联机Redo日志间的距离,减少了实例恢复所需的时间。

2.LOG_CHECKPOINT_INTERVAL参数值。

3.达到LOG_CHECKPOINT_TIMEOUT的延迟时。

4.最小的日志文件大小,当前Redo日志文件中已存在了大小为(LOG_CHECKPOINT_INTERVAL*OS块的大小(bytes))的数据。

5.Buffer Cache中的脏块的数量。

其它

其它的检查点包括实例和介质恢复检查点,当SCHEMA对象被DROPPED或TRUNCATED的时候的检查点。

DROP、TRUNCATE等。

(四)有关检查点的几个概念

1.RBA(Redo Block Address)、LRBA(Low RBA)、HRBA(High RBA)

RBA就是Redo日志块的地址,相当于数据文件中的ROWID,通过RBA可以定位Redo日志块。RBA由3部分组成:

l 日志文件序列号(4字节),根据这个可以找到对应的日志文件地址。

l 日志文件块编号(4字节),根据这个可以找到对应日志条目所在的日志文件块。

l Redo日志记录在日志块中的起始偏移字节数(2字节),根据这个可以找到对应的日志条目。

在Buffer Cache中,一个脏块第一次被更新的时候会产生Redo日志记录,该记录在Redo日志文件中所对应的位置就称为LRBA;若数据库再次更新该脏块的时候也会产生Redo日志记录,则该记录在Redo日志文件中所对应的位置就称为HRBA。

例如,用户发出了一条UPDATE命令,更新了块A,块A现在变成了脏块,Oracle会为它生成一条Redo记录。这条Redo记录在Redo日志文件中的位置就是RBA。过了一会儿,假如块A依然还是脏块,此时,用户又发出一条更新块A的命令,这又会生成一条Redo记录。第一条更新命令对应的Redo记录的RBA被称为块A的LRBA(Low RBA),第二条更新命令对应的RBA被称为HRBA(High RBA)。

2.Checkpoint RBA

当一个检查点事件发生的时候,CKPT进程会记录下当时所写的Redo日志块的地址即RBA,此时记录的RBA被称为Checkpoint RBA。从上一个Checkpoint RBA到当前的Checkpoint RBA之间的日志所保护的Buffer Cache中的脏块接下来将会被写入到数据文件当中去。

3.检查点队列(Checkpoint Queue,CKPTQ)

Oracle将所有在Buffer Cache中被修改的脏块按照LRBA的顺序连接起来就组成了一个检查点队列(CKPTQ),这个队列主要记录了Buffer Cache第一次发生变化的时间顺序,然后由DBWn进程根据CKPTQ顺序将脏块写入到数据文件中,这样保证了先发生变更的Buffer能先被写入到数据文件中。

Oracle引入检查点队列(CKPTQ)是为了使检查点在Buffer Cache较大的情况下依然有效工作(既要保证数据完全恢复,又要尽量快速恢复),该队列上存放的都是脏块所对应的Buffer Header。每次DBWn写脏块时,也是从CKPTQ上扫描脏块,并将这些脏块实际写入数据文件。检查点队列上的Buffer Header是按照数据块第一次被修改的时间的先后顺序来排列的。越早修改的数据块的Buffer Header排在越前面,同时如果一个数据块被修改了多次的话,那么在该链表上也只出现一次,其在CKPTQ上的位置不会发生变化。而且,检查点队列上的Buffer Header还记录了脏块在第一次被修改时,所对应的Redo条目在Redo日志文件中的地址,也就是LRBA,所以可以认为检查点队列上的脏块是按照LRBA排列的,从而保证最早更改的数据块能够尽快从内存写入数据文件。

DBWn每到一定的时机,就会被触发,沿着检查点队列(CKPTQ)的顺序刷新脏块,同时CKPT进程监控着检查点队列的长度,当检查点队列的长度达到一定限制时,CKPT会通知DBWn写脏块。CKPT会根据几个参数的设置和I/O的速度以及繁忙程度,计算出来一个Target RBA(目标RBA),DBWn会沿着检查点队列,按照Dirty Buffer的LRBA顺序将所有Target RBA之前对应的脏块从内存写入数据磁盘文件。当CKPT进程通知完DBWn后,CKPT的任务就结束了,它并不会等待DBWn写完所有的Target RBA之前的脏块。因此这里CKPT只是起到了一个通知DBWn进程写入的作用。

4.文件检查点队列(File Queue,FILEQ)

文件检查点队列提高了表空间检查点(Tablespace Checkpoint)的性能,每个Dirty Buffer同时链接到CKPTQ和FILEQ,CKPTQ包含实例所有需要执行检查点的Buffer,FILEQ包含属于特定文件需要执行检查点的Buffer。每个文件都包含一个文件队列,在执行表空间检查点请求时需要使用FILEQ,通常当对表空间执行OFFLINE等操作时会触发表空间检查点。CKPTQ和FILEQ都是双向链表,每个队列中都记录了两个地址信息,分别是前一块和下一块Buffer的地址信息。注意只有Dirty Buffer才会包含CKPTQ信息,否则为NULL,信息类似"ckptq:[NULL]fileq:[NULL]"。

5.On Disk RBA

“On Disk RBA”指向Redo日志文件里最新的(最后的)一条Redo日志条目,它是CKPT进程从某一个脏块里读取过来的,在进行恢复时应用Redo至少要达到这个值。在实例崩溃后,再次启动数据库,Oracle会到控制文件中读取LRBA,这就是检查点位置。从此处开始应用Redo日志,应用到On Disk RBA的位置,On Disk RBA是Oracle前滚操作的终点。如果某条Redo记录的RBA高于On Disk RBA,那么说明此Redo记录还没有被LGWR写进日志文件中,还驻留在Log Buffer中,所以,崩溃发生时,它是不可能被恢复的。

(五)完全检查点

完全检查点的工作过程如下所示:

第一阶段,CKPT进程开始一个检查点事件,并记录下Checkpoint RBA,这个通常是当前的RBA。

第二阶段,CKPT进程通知DBWn进程将所有Checkpoint RBA之前的Buffer Cache里面的所有脏块(已修改的数据库块)写入磁盘。

第三阶段,确定脏块都被写入磁盘以后进入到第三阶段,CKPT进程将检查点信息(Checkpoint Information)写入(或更新)数据文件头和控制文件中,以反映上一个检查点发生的时间(SCN)。

更新SCN的操作由CKPT进程完成,在Oracle 8i之后CKPT进程默认是被启用的,如果CKPT进程没有启用的话那相应的操作将由LGWR进程完成。

在Oracle 8i之前,数据库发生的检查点都是完全检查点。完全检查点会将Buffer Cache里面所有的脏块写入相应的数据文件中,同时将最新的Checkpoint SCN更新到所有的数据文件头部及控制文件,保证数据库处于一致的状态。需要注意的是,在完全检查点产生的时候,CKPT进程并不是把当前完全检查点发生那一时刻的SCN更新到控制文件和数据文件头,而是将这个触发检查点时刻DBWn当前刚写完Dirty Buffer对应的SCN更新到控制文件和数据文件头,也就是说,更新控制文件和数据文件头的SCN是滞后于完全检查点的发生那一时刻的SCN的。需要注意的是,在Oracle 8i之前,由于没有检查点队列(CKPTQ),也没有增量检查点的概念,所以,在发生完全检查点时,DBWn会以一种无序的方式将所有的Dirty Buffer写出到数据文件,这个时候Oracle会冻结所有DML操作等候所有Dirty Buffer被写出,巨大的I/O往往会影响到数据库的性能。后来随着Oracle数据库的发展和Buffer Cache的不断增大,Oracle意识到这个单一的完全检查点机制已经不能满足需要,所以在Oracle 8i后提出增量检查点的概念,建立了检查点队列(CKPTQ),让Dirty Buffer Header根据首次变化时候的顺序(LRBA)排列在Queue里面。这样DBWn只要顺着Queue的顺序写,而其它进程不必等候DBWn的写完成就可以继续。因此增量检查点的概念就由此产生了。

需要注意的是,日志切换会导致Checkpoint事件发生,但是Checkpoint发生却不会导致日志切换。日志切换触发的是完全检查点,而不是增量检查点,只不过Log Switch Checkpoint的优先级非常低,当一个Log Switch Checkpoint发生的时候它并不会立即的通知DBWn进程去写数据文件,但是当有其它原因导致Checkpoint或者是写入数据文件的RBA超过Log Switch Checkpoint的Checkpoint RBA的时候,这次的Log Switch Checkpoint将会被标记成完成状态,同时更新控制文件和数据文件头。

(六)增量检查点

为了能够尽量减少实例崩溃后恢复的时间,Oracle还引入了增量检查点(Incremental Checkpoint),从而增加了检查点启动的次数。如果每次检查点启动的间隔时间过长的话,那么可能会使得实例恢复的时间过长。因为前一次检查点启动以后,标识出了这个起点,然后在第二次检查点启动之前,DBWn可能已经将很多脏块已经写入了数据文件,而假如在第二次检查点启动之前发生实例崩溃,导致在日志文件中,所标识的起点仍然是上一次检查点启动时所标识的,导致Oracle不知道这个起点以后的很多Redo条目所对应的脏块实际上已经写入了数据文件,从而使得Oracle在实例恢复时重复地处理一遍,效率低下,浪费时间。

在执行增量检查点时,DBWn从检查点队列按照LRBA顺序写出,先修改的数据可以被按优先顺序写出,全局检查点因此可以不被增进。同时CKPT进程阶段性使用轻量级控制文件更新协议将当前LRBA写入控制文件,CKPT在进行轻量级更新时,不会更新控制文件中数据文件检查点信息(数据库SCN以及数据文件条目的SCN信息)以及数据文件头信息,而只是每3秒由CKPT进程检查DBWn写进度并更新控制文件中的检查点的位置信息(LRBA)。完全检查点会将检查点信息写入到控制文件以及数据文件头中;增量检查点只会将LRBA信息写入到控制文件中。

通过增量检查点,数据库可以将全部写出改为增量渐进写出,从而极大减少对于数据库性能的影响,而检查点队列进一步将RBA和检查点关联起来,从而可以通过检查点确定实例恢复的起点。

增量检查点的几个作用:

① CKPT每3秒一次的检查DBWn写进度并在控制文件中记录检查点位置(LRBA)。注意,增量检查点并不会去更新数据文件头,以及控制文件中数据库SCN以及数据文件条目的SCN信息。

② CKPT定期触发DBWn去写CKPTQ中的脏数据。

(七)检查点调优

检查点的主要任务就是催促DBWn刷新脏块,如果DBWn刷新脏块时的等待事件太多,那么就说明脏块太多、存储设备的写速度太慢,或者就是增量检查点的频率设置不合理。DBWn写脏块的等待事件是db file parallel write。如果系统增量检查点频率很低,系统大量产生该事件,在排除了存储设备写性能的问题后,那么应该将增量检查点频率设置的高一些。反之,如果增量检查点频率本身很高,若出现了db file parallel write事件,则说明检查点频率太高了。

除它之外,还有一个write complete waits事件,当前台进程要修改DBWn正要成批写的块中的若干个块时,就会有此等待事件,这个事件是前台进程在等待DBWn写完成。如果这个等待事件太多,那么说明了存储设备写性能有问题,或者增量检查点太过频繁了。

1. 与增量检查点相关的参数

优化检查点涉及到下面4个关键初始化参数:

① FAST_START_MTTR_TARGET

② LOG_CHECKPOINT_INTERVAL

③ LOG_CHECKPOINT_TIMEOUT

④ LOG_CHECKPOINTS_TO_ALERT

需要注意的是,日志文件切换将始终覆盖由以上4个参数引起的检查点。这4个参数的详解见下表:

参数

简介

FAST_START_MTTR_TARGET

通过参数FAST_START_MTTR_TARGET可以指定数据库执行单实例的崩溃恢复所要花费的秒数(由后台进程SMON实现),可以认为是一个加快实例恢复的参数。基于内部统计信息,增量检查点会自动调整检查点目标,以满足FAST_START_MTTR_TARGET的要求。在Oracle 8i中,初始化参数FAST_START_IO_TARGET会使增量检查点自动调整其目标,从而使恢复所需的数据块数量不多于FAST_START_IO_TARGET设置的值。自Oracle 9i开始,已弃用此参数,取而代之的是参数FAST_START_MTTR_TARGET,并且该参数已成为优化增量检查点目标的首选方法。

从Oracle 10g开始,FAST_START_MTTR_TARGET默认值为0,即开启自调节检查点(self-tune checkpointing),自调节检查点对应隐含参数为“_DISABLE_SELFTUNE_CHECKPOINTING”,该值默认为FALSE。

若显式设置FAST_START_MTTR_TARGET为0,则在告警日志中会有提示:

MTTR advisory is disabled because FAST_START_MTTR_TARGET is not set

若该参数设置为非0,则表示开启MTTR Advisory(STATISTICS_LEVEL参数必须为TYPICAL或者ALL),此时告警日志中不会再有以上信息提示了。若该值设置太小,则也会有提示:

FAST_START_MTTR_TARGET 45 is set too low, using minimum achievable MTTR 48 instead.

对于MTTR以下视图比较重要:

l V$INSTANCE_RECOVERY.ESTIMATED_MTTR显示当前预计的平均恢复时间(MTTR,Mean Time To Recovery,以秒为单位)。即使未指定FAST_START_MTTR_TARGET,也同样会显示此值。

l V$INSTANCE_RECOVERY.TARGET_MTTR显示由系统强制执行的有效MTTR目标(以秒为单位)。

l V$MTTR_TARGET_ADVICE显示在当前的MTTR设置下由当前的工作负载产生的I/O数量,以及在其它MTTR设置下将由当前的工作负载产生的预计I/O数量。此视图可帮助用户在运行时性能和设置FAST_START_MTTR_TARGET以实现快速恢复之间进行权衡。若未开启MTTR Advisory则此视图内容为空。

LOG_CHECKPOINT_INTERVAL

该参数指定增量检查点目标应滞后于当前日志尾的最大Redo块数量。如果指定了FAST_START_MTTR_TARGET,那么就不应设置LOG_CHECKPOINT_INTERVAL或将其设置为0。在大多数Unix系统上,操作系统块大小都是512字节。也就是说,如果将LOG_CHECKPOINT_INTERVAL的值设置为10000,那么就意味着增量检查点目标相对于当前日志尾的滞后不得超过5M。以此计算,如果Redo日志的大小为20M,那么会对每个日志产生4个检查点。LOG_CHECKPOINT_INTERVAL会影响检查点的发生时间,这意味着应特别注意此参数的设置,保持其随Redo日志文件的大小变化而更新。检查点的频率是影响数据库从意外故障中恢复所需时间的因素之一。检查点之间的间隔越长,则在发生系统崩溃时,数据库恢复所需的时间就越长。检查点间隔越短意味着数据库的恢复速度越快,但是代价是检查点操作会消耗更多的资源。此参数还会影响在恢复的前滚阶段期间完成数据库恢复操作所需的时间。实际的恢复时间取决于此时间,以及其它因素,例如故障类型(实例或系统崩溃、介质故障等)以及需要应用的归档Redo日志数量。

LOG_CHECKPOINT_TIMEOUT

该参数指定增量检查点目标应滞后于当前日志尾的最长秒数。换句话说,它指定缓冲区缓存中的脏缓存可以保持脏状态的时间。检查点频率影响数据库从意外故障中恢复所需的时间。检查点之间的间隔越长,数据库恢复所需的时间就越多。Oracle建议使用LOG_CHECKPOINT_INTERVAL而不是LOG_CHECKPOINT_TIMEOUT来控制检查点间隔,后者会每“n”秒启动一次检查点,而不管事务频率。这可能会导致在事务量变化的情况下出现不必要的检查点。只要可能,就必须避免不必要的检查点,以实现最佳性能。

许多人会有这样一种误解:将LOG_CHECKPOINT_TIMEOUT设置为给定值之后,系统就会按该间隔启动日志切换,从而启用用于standby数据库配置的恢复窗口。日志切换会引起检查点,但检查点并不会引起日志切换。引起日志切换的唯一方式是使用ALTER SYSTEM SWITCH LOGFILE进行手动操作或重新调节Redo日志大小,以引起更为频繁的切换。这由操作系统块而非时间间隔控制。

LOG_CHECKPOINTS_TO_ALERT

通过该参数可以将检查点记录到alert日志中。这样做有助于确定检查点是否按所需频率发生。在Oracle 9i之前,此参数为静态参数。Oracle通常建议将此参数设置为TRUE,因为开销很小,可以忽略不计,但alert日志中的信息可能会非常有用。

需要注意的是,除了上表中列出的4个初始化参数外,Oracle内部事实上还将Redo日志文件末尾前面90%的位置设为检查点位置(90% Of Smallest Redo Log)。在每个Redo日志中,这几个参数指定的位置可能不尽相同,Oracle将离日志文件末尾最近的那个位置确认为检查点位置。

在Oracle 9i后,对检查点频率建议只设置FAST_START_MTTR_TARGET。当然,根据需要也可以通过参数LOG_CHECKPOINT_TIMEOUT设置一个脏块保持脏状态的最大时间,而参数LOG_CHECKPOINT_INTERVAL建议不再使用,参数FAST_START_IO_TARGET已废弃。

2. Redo日志和检查点

在每次切换日志时都会发生一次Thread检查点。如果上一个检查点已在进行中,那么由日志切换引起的检查点将覆盖当前检查点。此时就需要大小合适的Redo日志,以避免因频繁的日志切换而引起不必要的检查点。另外,增量检查点目标和日志尾之间的间隔也会受“最小在线日志文件大小的90%”设置所限制。这样可确保在大多数情况下,日志切换不必等待检查点。因此,日志文件大小应配置得足够大。一个经验值是,最多每15~20分钟切换一次日志。日志文件过小会增加检查点活动并降低数据库的性能。Oracle建议用户将所有在线日志文件设置为同一大小,且每个线程至少拥有两个日志组。若要监视日志切换发生的速度,以及随后的检查点发生的速度,则告警(alert)日志是一个很有价值的工具。

以下是通过alert日志发现日志切换过于频繁的示例:

1  Fri May 16 17:15:43 1997<br> 2Thread 1 advanced to log sequence 1272<br> 3  Current log# 3 seq# 1272 mem# 0: /u01/oradata/lhrdb/logs/redologs03.log<br> 4Thread 1 advanced to log sequence 1273<br> 5  Current log# 1 seq# 1273 mem# 0: /u01/oradata/lhrdb/logs/redologs01.log<br> 6Fri May 16 17:17:25 1997<br> 7Thread 1 advanced to log sequence 1274<br> 8  Current log# 2 seq# 1274 mem# 0: /u01/oradata/lhrdb/logs/redologs02.log<br> 9Thread 1 advanced to log sequence 1275<br>10  Current log# 3 seq# 1275 mem# 0: /u01/oradata/lhrdb/logs/redologs03.log<br>11Fri May 16 17:20:51 1997<br>12Thread 1 advanced to log sequence 1276<br>13  Current log# 1 seq# 1276 mem# 0: /u01/oradata/lhrdb/logs/redologs01.log<br>