一、检查点概述
大多数关系型数据库都采用“在提交时并不强迫针对数据块的修改完成”而是“提交时保证修改记录(以重做日志的形式)写入日志文件”的机制,来获得性能的优势。这句话的另外一种描述是:当用户提交事务,写数据文件是“异步”的,写日志文件是“同步”的。这就可能导致数据库实例崩溃时,内存中的DB_Buffer中的修改过的数据,可能没有写入到数据块中。数据库在重新打开时,需要进行恢复,来恢复DB Buffer中的数据状态,并确保已经提交的数据被写入到数据块中。检查点是这个过程中的重要机制,通过它来确定,恢复时哪些重做日志应该被扫描并应用于恢复。

要了解这个检查点,首先要知道checkpoint queu概念,检查点发生后,触发dbwn,CKPT获取发生检查点时对应的SCN,通知DBWn要写到这个SCN为止,dbwr写dirty buffer是根据buffer在被首次modify的时候的时间的顺序写出,也就是buffer被modify的时候会进入一个queue(checkpoint queue),dbwr就根据queue从其中批量地写到数据文件。由于这里有一个顺序的关系,所以dbwr的写的进度就是可衡量的,写到哪个buffer的时候该buffer的首次变化时候的scn就是当前所有数据文件block的最新scn,但是由于无法适时的将dbwr的进度记录下来,所以oracle选择了一些策略。其中就包括ckpt进程的检查点和心跳。

但oracle考虑到检查点scn的间隔还是太大了,因为检查点的触发条件有限,周期可能比较长,有些情况下比如检查点需要5分钟才触发,那这个时候系统crash再重新启动就意味着很可能系统需要5分钟才能启动。

于是oracle采用了一个心跳的概念,以3秒的频率将dbwr写的进度反应到控制文件中,这样系统crash重新启动的时候将从更近的一个时间点开始恢复。

再这里同样需要说明的一点是dbwr并不是只有当检查点发生的时候才写,它大约有10几种条件触发写操作

所以这个问题,我们需要理解的是oracle为什么要这么做?
oracle的目的就是缩短崩溃恢复时间!

oracle如何缩短恢复时间?
1:检查点机制
2:心跳机制
 
oracle为什么不适时的将dbwr写进度反应到文件中?
适时反应成本太高!3秒种是一个合适的值,可以接受,代价不高又能大大缩短崩溃后恢复时间。

检查点发生以后,CKPT进程检查checkpoint queue(也就是脏块链表)是否过长,如果是,则触发DBWn,将一部分脏块写入数据文件,从而缩短checkpoint queue。

checkpoint发生时,一方面通知dbwr进行下一批写操作,(dbwr写入的时候,一次写的块数是有一个批量写的隐藏参数控制的。)另一方面,oracle采用了一个心跳的概念,以3秒的频率将dbwr写的进度反应到控制文件中,也就是把dbwr当前刚写完的dirty buffer对应的scn写入数据文件头和控制文件,这就是检查点scn。

这个3秒和增量检查点不是一个概念,3秒只是在控制文件中,ckpt进程去更新当前dbwr写到哪里了,这个对于ckpt进程来说叫heartbeat,heartbeat是3秒一次,3秒可以看作不停的检查并记录检查点执行情况(DBWR的写进度)。

检查点发生之后数据库的数据文件、控制文件处于一致状态的含义是不需要进行介质恢复,只表示数据文件头一致,但是并不表示数据文件内容一致,因为数据文件内容可能在没有发生检查点的其他情况下的dbwr写数据文件,这样数据文件内容就不一致,若掉电需要进行崩溃恢复。
 
二、触发的条件
这里需要明白两个概念“完全检查点和增量检查点”的区别。
 
1、增量检查点(incremental checkpoint)
oracle8之前,那时候没有chekpoint queue,也没有增量的概念,dirty buffer的写出是无顺的,就是冻结所有dml等候所有dirty buffer被写出。后来随着数据库规模的扩展和buffer cache的不断增大,oracle意识到这个机制已经满足不了需要,所以提出增量检查点的概念,建立了checkpoint queue,让dirty buffer header根据首次变化时候的顺序排列在queue里面。这样dbwr只要顺着queue的顺序写,而其他进程不必等候dbwr的写操作完成就可以继续。

自从有了checkpoint queue之后,检查点就成为一个短暂的动作,就是通知dbwr你要继续写dirty buffer到当前检查点发生时候的scn,然后将当前dbwr刚写完的dirty buffer对应的scn,写进数据文件和控制文件【增量检查点时不写数据文件头】(比如日志切换这种动作引起的检查点动作等)。然后检查点动作就结束了。剩下的工作就交给DBWn了,检查点进程也不必等候dbwr的完成。

ckpt进程通知dbwr之后并不需要等待dbwr写到当前这个检查点对应的时间点。所以ckpt可以将已经完成的最后一个检查点scn写到控制文件和数据文件(可能是上一个,也可能是上上个,总之dbwr完成了哪个算哪个)。这样本次需要写进数据文件的dirty buffer可能在下一次检查点发生的时候已经写完了,这样下一次检查点发生的时候就把本次的检查点scn更新到控制文件和数据文件。

在oracle 8之前,没有ckpt queue,只有LRU list,而LRU list里面的Dirty Buffer是不按时间顺序排列的,所以checkpoint时都会做一个full thread checkpoint,将LRU list中的所有buffer写到数据文件。

在oracle8i以后,当发生FULL CHECKPOINT时,oracle只是获取系统当前的SCN,然后将这个SCN之前的脏数据块写入磁盘,后续的DML脏数据块继续入队,他并不是保证整个缓冲区没有脏块,只是保证此检查点发生之前这段距离间没有脏块,而在checkpoint点之后的DML可以正常操作。

oracle8以后推出了incremental checkpoint的机制,在以前的版本里每checkpoint时都会做一个full thread checkpoint,这样的话所有脏数据会被写到磁盘,巨大的i/o对系统性能带来很大影响。为了解决这个问题,oracle引入了checkpoint queue机制,每一个脏块会被移到检查点队列里面去,按照low rdb(第一次对此块修改对应的redo block address)来排列,靠近检查点队列尾端的数据块的low rba值是最小的,而且如果这些赃块被再次修改后它在检查点队列里的顺序也不会改变,这样就保证了越早修改的块越早写入磁盘。每隔3秒钟ckpt会去更新控制文件和数据文件,记录checkpoint执行的情况。

在运行的Oracle数据中,有很多事件、条件或者参数来触发检查点。比如
(1)当已通过正常事务处理或者立即选项关闭例程时;(shutdown immediate或者Shutdown normal;)
(2)当通过设置初始化参数LOG_CHECKPOINT_INTERVAL、LOG_CHECKPOINT_TIMEOUT和FAST_START_IO_TARGET强制时;
(3)当数据库管理员手动请求时;(ALter system checkpoint)
(4)alter tablespace ... offline;
(5)每次日志切换时;(alter system switch logfile)
需要说明的是,
alter system switch logfile也将触发完全检查点的发生。
alter database datafile ... offline不会触发检查点进程。

如果是单纯的offline datafile,那么将不会触发文件检查点,只有针对offline tablespace的时候才会触发文件检查点,这也是为什么online datafile需要media recovery而online tablespace不需要。

对于表空间的offline后再online这种情况,最好做个强制的checkpoint比较好。
上面几种情况,将触发完全检查点,促使DBWR将检查点时刻前所有的脏数据写入数据文件。

另外,一般正常运行期间的数据库不会产生完全检查点,下面很多事件将导致增量检查点,比如:
在联机热备份数据文件前,要求该数据文件中被修改的块从DB_Buffer写入数据文件中。所以,发出这样的命令:
ALTER TABLESPACE tablespace_name BIGEN BACKUP & end backup;也将触发和该表空间的数据文件有关的局部检查点;另外,
ALTER TABLESPACE tablespace_name READ ONLY;
ALTER TABLESPACE tablespace_name OFFLINE NORMAL;
等命令都会触发增量检查点。

注意:
每隔三秒也会触发检查点,但是并没有被oracle正式作为一种检查点的触发方式列入文档,并且这个3秒是记录dbwr进度而不是通知dbwr写。
这是三秒触发的检查点与其它条件触发检查点不同的地方。
 

三、关于low cache rba与on disk rba的理解:
简单说:
low cache rba就是CKPT记录的DBWR写的进度。
on disk rba就是LGWR的写进度。

如果数据库carsh,low cache rba是恢复的起点,on disk rba是恢复的终点。

阐述一下:dbwr成功写完后并不把此刻scn信息写到控制文件中,只有CKPT才更新控制文件和数据文件头,dbwr只要成功将dirty data写入数据文件就是成功,CKPT只要能将最新DBWR写完的SCN更新到控制文件和数据文件头就算成功。但是由于CKPT进程不是实时更新dbwr写完的scn到控制文件中,而是采用每3妙更新一次的策略,因此最后有ckpt进程写进控制文件的scn信息有可能不是当前dbwr刚刚写完的scn值。这点应该注意,也就是说dbwr写的进度与ckpt进程更新控制文件的进度是不同的。

关于检查点的一点具体应用讨论:
1、Commit成功后,数据还会丢失吗?

对于Oracle来说,用户所做的DML操作一旦被提交,则先是在database buffer cache中进行修改,同时在修改之前会将数据的前镜像保存在回滚段中,然后将修改之前和修改之后的数据都写入到redo log buffer中,当接收到commit命令之后,则redo log buffer开始写redo log file,并且记录此时的scn,当redo log file写完了之后,表示这次事务提交操作已经确认被数据库记录了,只有当redo log file写成功了,才会给用户Commit completed的成功字样。而对于Database buffer cache中的dirty buffer则会等待触发DBWn才写入,但是如果此时断电,则数据已经被记录到了redo log file中,系统在重新启动的时候,会自动进行嵌滚和回滚来保证数据的一致。所以,只要是commit成功的了,数据不会丢失!

2、数据库发生一次DBWn,是否将所有buffer cache中的dirty buffer都写入,还是先将脏队列中的数据写入?
这话看起来有道理,但实际上,dbwr在写的时候又不断地在产生dirty buffer ,所以说检查点发生的时候是期望把该时间点之前的所有脏缓冲区写入数据文件。
所有的buffer,不在LRU list上就在dirty list上, dbwr写入的时候,一次写的块数是有一个批量写的隐藏参数控制的。
所以说要是dbwr将dirty list也好,lru list上的也好,要实现全部写入,都是一个现实系统中很难存在的现象。dirty总是在不断的产生,dbwr总是在不断地写,增量检查点发生的时候也并不意味着一定要更新数据文件头,检查点开始的时候只表示该次检查点结束的时候要更新数据文件头的话数据文件头具有该时间点的一致性。

3、data block里面不是也有SCN吗?和文件头里面的SCN有什么关系?什么时候被更新?代表的是是什么含义?
data block里面的SCN是当block被更改的时候的SCN
而数据文件有那么多block,自然不同的block有不同的SCN
block中存在block SCN和ITL中的commit SCN
block SCN又在块头和块位都有,若不一致意味着block损坏(热碑可能出现这个情况,需要从redo log中拷贝回来,若是正在修改的过程中由于进程死掉则pmon负责清理。若由于一些以外发生这样的不一致的情况,则查询的时候出现1578错误,当然该错误号也可能是物理磁盘损坏,这里表示逻辑的损坏!)

这个头和尾的SCN的检查时机跟这两个参数有关:
db_block_checking boolean FALSE
db_block_checksum boolean FALSE
该两参数信息请查阅http://tahiti.oracle.com
而ITL中的commit SCN则跟consistent gets and delay block cleanout有关
数据文件头的SCN是检查点发生时更新的,代表着当恢复的时候从这个SCN点开始在log file中寻找redo开始做恢复。

4、怎么解释这个现象?看下面的操作!
___________________________________________________________
sys@DBAP01>select max(ktuxescnw*power(2,32)+ktuxescnb)from x$ktuxe;
MAX(KTUXESCNW*POWER(2,32)+KTUX
------------------------------
52211024
已用时间:  00:00:00.00
sys@DBAP01>alter system checkpoint;
系统已更改。
已用时间:  00:00:00.06
sys@DBAP01>select CHECKPOINT_CHANGE# from v$database;
CHECKPOINT_CHANGE#
------------------
52211055
已用时间:  00:00:00.00
sys@DBAP01>select max(ktuxescnw*power(2,32)+ktuxescnb)from x$ktuxe;
MAX(KTUXESCNW*POWER(2,32)+KTUX
------------------------------
52211053
已用时间:  00:00:00.00
sys@DBAP01>
 
解答:x$ktuxe计算出来的是已经结束的最新的事务的commit scn,所以可小于当前系统scn。检查点scn自然也小于当前系统scn。但是检查点scn和x$ktuxe计算出来的大小却倚赖于系统状况了。
current scn是系统当前所产生的最大scn,可能是当前未结束事务所产生的scn。在9i的dbms_flashback.get_system_number可以得到这个值,这个值应该是大于等于x$ktuxe SCN (这个view记录的是当前数据库结束事务的最大scn)