检查点
检查点只是一个数据库事件,它存在的根本意义在于减少崩溃恢复(crash recovery)时间.检查点事件由ckpt后台进程触发,当检查点发生时,ckpt进程会负责通知dbwr进程将脏数据(dirty buffer)写出到数据文件上,ckpt进程的另外一个职责是负责更新数据文件头及控制文件上的检查点信息.
检查点(checkpoint)的工作原理
在oracle数据库中,当进行数据修改时,需要首先将数据读入内存中(buffer cache),修改数据的同时,oracle会记录重做(redo)信息用于恢复.因为有了重做信息的存在,oracle不需要在事务提交时(commit)立即将变化的数据写回磁盘(立即写的效率会很低),重做的存在也正是为了在数据库崩溃之后,数据可以恢复.
常见的情况,数据库可能因为断电而crash,那么内存中修改过的,尚没有写入数据文件的数据将会丢失.在下一次数据库启动之后,oracle可以通过重做(redo)日志进行事务重演(也就是进行前滚),将数据库恢复到崩溃之前的状态,然后数据库可以打开提供使用之后oracle可以将没有提交的事务进行回滚.
检查点的存在就是为了缩短这个恢复时间.当检查点发生时(此时的scn被称为checkpoint scn). oracle会通知dbwr进程,把修改过的数据,也就是此checkpoint scn之前的脏数据(dirty data)从buffer cache写入磁盘,当写入完成之后,ckpt进程则会相应更新控制文件和数据文件头,记录检查点信息,标识变更.
checkpoint scn可以从数据库中查询得到:
SQL> select file#,checkpoint_change#,to_char(checkpoint_time,'yyyy-mm-dd hh24:mi:ss') checkpoint_time from v$datafile; FILE# CHECKPOINT_CHANGE# CHECKPOINT_TIME ---------- ------------------ ------------------- 1 1131856 2013-01-18 01:33:36 2 1131856 2013-01-18 01:33:36 3 1131856 2013-01-18 01:33:36 4 1131856 2013-01-18 01:33:36 5 1131856 2013-01-18 01:33:36 6 1131856 2013-01-18 01:33:36 6 rows selected. SQL> select dbid,checkpoint_change# from v$database; DBID CHECKPOINT_CHANGE# ---------- ------------------ 3172629284 1131856
在检查点完成之后,此检查点之前修改过的数据都已经写回磁盘,重做日志文件中的相应重做记录对于崩溃/实例恢复不再有用.
如果检查点的频率高,那么恢复时间需要应用的重做日志就相对得少,恢复时间就可以缩短,如果oracle可以在性能允许的情况下,使得检查点的scn接宾redo的最新变更,那么,使得oracle可以最大化地减少恢复时间.
常规检查点与增量检查点
脏缓冲列表(Dirty List).
当数据在buffer cache中被修改之后,Dirty Buffer会被转移到dirty list,以便将来
执行的检查点可以将这些修改过的buffer写出到数据文件上.
但是注意,由于dirty list上的buffer并没有顺序,有的buffer反复被修改,在链表上的位置就可能发生变化,当检查点发生时,oracle需要将脏缓冲列表上的数据全部写出到数据文件.为了区分,在oracle8之前,oracle实施的这类检查点通常被称为常规检查点(Conventional checkpoint),由于检查点时需要写出全部的脏数据,所以也被称为完全检查点(complete checkpoint),常规检查点按特定的条件触(log_checkpoint_interval,
log_checkpoint_timeout参数设置及log switch等条件触发),触发时会同时更新数据文件头以及控制文件记录检查点信息.
从oracle8开始,oracle引入了增量检查点(Incremental checkpoint)的概念.和以前的
版本相比,在新版本中,主要的变化是引入了检查点队列(checkpoint queue ckptq)机制.在数据库内部,每一个脏数据块都会被记录到检查点队列中,按照LRBA(Low RBA,第一次对此数据块修改所对应的redo byte address)的顺序来排列,如果一个数据块进行过多次修改,该数据块在检查点队列上的顺序并不会发生变化(相对LRBA,
后面修改的RBA被称为HRBA)
当执行增量检查点时,dbwr从检查点队列按照Low RBA的顺序写出,此时先修改的数据就可以被按顺序优先写出,实例检查点因此可以不断增进,同时,ckpt进程也阶段性地使用非常轻量级的控制文件更新协议,将当前的最低的RBA写控制文件.为了减少频繁增量检查点的性能影响,ckpt在进行轻量级更新时,并不会改写控制文件中数据文件
的检查点信息以及数据文件头信息.而只是记录控制文件检查点scn(controlfile checkpoint at scn)并且根据增量检查点的写出增进RBA信息.
通过增量检查点,数据库可以将以前的全量写出变更为增量渐进写出,从而可以极大工减少对于数据库性能的影响;而检查点队列则进一步地将RBA和检查点关联起来,从而可以通过检查点来确定恢复的起点.
检查点队列在数据库内部通过latch保护:
select name,gets,misses from v$latch where name='checkpoint queue latch'; SQL> select name,gets,misses from v$latch where name='checkpoint queue latch'; NAME GETS MISSES -------------------------------------------------- ---------- ---------- checkpoint queue latch 86382 0
checkpoint queue latch存在多个子latch,可以通过v$latch_children视图查询:
select name,gets,misses from v$latch_children where name='checkpoint queue latch'; SQL> select name,gets,misses from v$latch_children where name='checkpoint queue latch'; NAME GETS MISSES -------------------------------------------------- ---------- ---------- checkpoint queue latch 4550 0 checkpoint queue latch 4550 0 checkpoint queue latch 4550 0 checkpoint queue latch 4550 0 checkpoint queue latch 11374 0 checkpoint queue latch 11724 0 checkpoint queue latch 4550 0 checkpoint queue latch 4550 0 checkpoint queue latch 4550 0 checkpoint queue latch 4550 0 checkpoint queue latch 4550 0 checkpoint queue latch 4550 0 checkpoint queue latch 4550 0 checkpoint queue latch 4550 0 checkpoint queue latch 4550 0 checkpoint queue latch 4550 0 16 rows selected.
除了检查点队列(ckptq)之外,数据库中还存在另外一个队列和检查点相关,这就是文件检查点队列(file queue),通常缩写为fileq,文件检查点队列的引入提高了表空间检查点(tablespace checkpoint)的性能.每个dirty buffer同时链接到这两个队列,ckptq包含实例所有需要执行检查点的buffer,fileq包含属于特定文件需要执行检查点的buffer,每个文件都包含一个文件队列,在执行表空间检查点请求时需要使用fileq,通常当对表空间执行offline等操作时会触发表空间检查点.
在buffer cache中,每个buffer的header上都存在ckptq以及fileq队列信息,通过如下命令可以转储buffer cache信息(注意应发仅在测试环境中尝试);
alter session set events 'immediate trace name buffers level 10'; SQL> alter session set events 'immediate trace name buffers level 10'; Session altered. SQL> select 2 d.value||'/'||lower(rtrim(i.instance, 3 chr(0)))||'_ora_'||p.spid||'.trc' trace_file_name 4 from ( select p.spid 5 from v$mystat m, 6 v$session s,v$process p 7 where m.statistic# = 1 and s.sid = m.sid and p.addr = s.paddr) p, 8 ( select t.instance from v$thread t,v$parameter v 9 where v.name = 'thread' and 10 (v.value = 0 or t.thread# = to_number(v.value))) i, 11 ( select value from v$parameter 12 where name = 'user_dump_dest') d 13 / TRACE_FILE_NAME -------------------------------------------------------------------------------- /u01/app/oracle/admin/jingyong/udump/jingyong_ora_4239.trc
以下bh信息来自oracle10g
BH (0x24ff61dc) file#: 3 rdba: 0x00c00a6c (3/2668) class: 1 ba: 0x24eee000 set: 3 blksize: 8192 bsi: 0 set-flg: 2 pwbcnt: 46 dbwrid: 0 obj: 8780 objn: 8780 tsn: 2 afn: 3 hash: [29115d3c,29115d3c] lru: [24ff62e0,24ff60d0] lru-flags: ckptq: [24ff5184,29150a60] fileq: [24ff518c,29150a9c] objq: [27b37888,27b37888] st: XCURRENT md: NULL tch: 35 flags: buffer_dirty gotten_in_current_mode block_written_once redo_since_read LRBA: [0x5.2e7.0] HSCN: [0x0.11546d] HSUB: [1] buffer tsn: 2 rdba: 0x00c00a6c (3/2668) scn: 0x0000.0011546d seq: 0x01 flg: 0x02 tail: 0x546d0601 frmt: 0x02 chkval: 0x0000 type: 0x06=trans data
注意信息中的ckptq和fileq,这就是检查点队列和文件队列,每个队列后面记录了两个地址信息,分别是前一块以及下一块的地址,通过这个信息ckptq和fileq构成了双向链表.注意仅仅只有dirty buffer才会包含ckptq信息,否则为null,信息类似如下
BH (0x24be637c) file#: 1 rdba: 0x004096b0 (1/38576) class: 1 ba: 0x2480a000 set: 3 blksize: 8192 bsi: 0 set-flg: 2 pwbcnt: 46 dbwrid: 0 obj: 18 objn: 18 tsn: 0 afn: 1 hash: [290c0878,290c0878] lru: [24be6320,24be6480] lru-flags: moved_to_tail ckptq: [NULL] fileq: [NULL] objq: [24be6374,24be64d4] st: XCURRENT md: NULL tch: 3 flags: only_sequential_access LRBA: [0x0.0.0] HSCN: [0xffff.ffffffff] HSUB: [65535] buffer tsn: 0 rdba: 0x004096b0 (1/38576) scn: 0x0000.00030101 seq: 0x01 flg: 0x04 tail: 0x01010601 frmt: 0x02 chkval: 0x8c8d type: 0x06=trans data [oracle@jingyong udump]$ grep ckptq jingyong_ora_4239.trc |grep -v NULL ckptq: [26c13b04,233f6364] fileq: [237e9e1c,237e9d6c] objq: [237e9de4,237ea0a4] ckptq: [233f6414,237e6be4] fileq: [233f641c,237e6bec] objq: [237e6c64,233f6494] ckptq: [237e9aa4,237e9cb4] fileq: [237e9aac,237e9cbc] objq: [237e9d34,27b8afa8] ckptq: [237e6c94,233f6154] fileq: [237e6c9c,233f615c] objq: [233f61d4,237e6d14] ckptq: [237e9944,237ea0d4] fileq: [237e994c,237ea0dc] objq: [237ea154,237e99c4] ckptq: [29150a60,237e9944] fileq: [29150a74,237e994c] objq: [27b1cc58,27b1cc58] ckptq: [26c13b04,26c18194] fileq: [26c13b0c,26c1819c] objq: [26c18214,26c13b84] ckptq: [26c12924,291529c8] fileq: [26c1292c,29152a40] objq: [27b1d0b8,26c129a4] ckptq: [26c18034,26c12874] fileq: [26c1803c,26c1287c] objq: [26c128f4,26c180b4] ckptq: [291529c8,26c12be4] fileq: [29152a40,26c12bec] objq: [26c12c64,27b1d0b8] ckptq: [233f6204,26c18034] fileq: [233f620c,26c1803c] objq: [26c180b4,233f6284] ckptq: [26c12b34,233f6204] fileq: [26c12b3c,233f620c] objq: [233f6284,26c12bb4] ckptq: [26c12be4,26c127c4] fileq: [26c12bec,26c127cc] objq: [26c12844,26c12c64] ckptq: [26c12874,233f6364] fileq: [26c1287c,233f636c] objq: [233f63e4,26c128f4] ckptq: [26c127c4,233f6414] fileq: [26c127cc,233f641c] objq: [233f6494,26c12844] ckptq: [29150a60,24ff50d4] fileq: [29150a9c,24ff50dc] objq: [27b37f18,27b37f18] ckptq: [24ff5184,29150a60] fileq: [24ff518c,29150a9c] objq: [27b37888,27b37888] ckptq: [233f6364,26c12924] fileq: [233f636c,26c1292c] objq: [26c129a4,233f63e4]
在sga中存在一块内存区域用于记录这个检查点队列
select name,bytes from v$sgastat where upper(name) like '%CHECKPOINT%'; SQL> select name,bytes from v$sgastat where upper(name) like '%CHECKPOINT%'; NAME BYTES -------------------------- ---------- Checkpoint queue 128320
从oracle10g开始,数据库中额外增加了对象检查点队列(object queue,objq)用于记录对象检查点信息:
BH (0x24ff61dc) file#: 3 rdba: 0x00c00a6c (3/2668) class: 1 ba: 0x24eee000 set: 3 blksize: 8192 bsi: 0 set-flg: 2 pwbcnt: 46 dbwrid: 0 obj: 8780 objn: 8780 tsn: 2 afn: 3 hash: [29115d3c,29115d3c] lru: [24ff62e0,24ff60d0] lru-flags: ckptq: [24ff5184,29150a60] fileq: [24ff518c,29150a9c] objq: [27b37888,27b37888] st: XCURRENT md: NULL tch: 35 flags: buffer_dirty gotten_in_current_mode block_written_once redo_since_read LRBA: [0x5.2e7.0] HSCN: [0x0.11546d] HSUB: [1] buffer tsn: 2 rdba: 0x00c00a6c (3/2668) scn: 0x0000.0011546d seq: 0x01 flg: 0x02 tail: 0x546d0601 frmt: 0x02 chkval: 0x0000 type: 0x06=trans data
共享池中分配了相关内存用于OBJECT QUEUE:
select * from v$sgastat where name like 'object queue%'; SQL> select * from v$sgastat where name like 'object queue%'; POOL NAME BYTES ------------ -------------------------- ---------- shared pool object queue hash table d 3040 shared pool object queue hash buckets 69632 shared pool object queue 16352
下面来看一下控制文件以及增量检查点的协同工作,以下输出来自oracle10g,两次level 8级控制文件的转储.
第一部分重要信息是控制文件的seq号,控制文件随着数据库的变化而增进版本.
[oracle@jingyong udump]$ diff jingyong_ora_4445.trc jingyong_ora_4463.trc 1c1 < /u01/app/oracle/admin/jingyong/udump/jingyong_ora_4445.trc --- > /u01/app/oracle/admin/jingyong/udump/jingyong_ora_4463.trc 13c13 < Unix process pid: 4445, image: oracle@jingyong (TNS V1-V3) --- > Unix process pid: 4463, image: oracle@jingyong (TNS V1-V3) 15,18c15,18 < *** 2013-01-18 05:40:06.832 < *** SERVICE NAME:(SYS$USERS) 2013-01-18 05:40:06.797 < *** SESSION ID:(159.15) 2013-01-18 05:40:06.797 < DUMP OF CONTROL FILES, Seq # 4498 = 0x1192 --- > *** 2013-01-18 05:44:49.284 > *** SERVICE NAME:(SYS$USERS) 2013-01-18 05:44:49.258 > *** SESSION ID:(159.21) 2013-01-18 05:44:49.258 > DUMP OF CONTROL FILES, Seq # 4499 = 0x1193 23c23 < Control Seq=4498=0x1192, File size=450=0x1c2 --- > Control Seq=4499=0x1193, File size=450=0x1c2 44c44 < Database checkpoint: Thread=1 scn: 0x0000.00114550 --- > Database checkpoint: Thread=1 scn: 0x0000.00115af8 接下来是控制文件检查点scn,增量检查点不断增进的内容之一: 66c66 < Controlfile Checkpointed at scn: 0x0000.001145c1 01/18/2013 05:38:56 --- > Controlfile Checkpointed at scn: 0x0000.00115af8 01/18/2013 05:44:37 96,97c96,97
检查点记录之后是RBA信息,检查点和redo相关联在这里实现,通过以下信息可以注意到,通过增量检查点之后,而low cache rba从0x5.307.0增进到0x5.412.0,low cache rba是下一次恢复的起点,而on disk rba则是指已经写入磁盘(redo log file)的rba地址.
这就是前滚恢复能够到达的终点.增量检查点的作用由此体现:
< low cache rba:(0x5.307.0) on disk rba:(0x5.407.0) < on disk scn: 0x0000.00115a04 01/18/2013 05:33:13 --- > low cache rba:(0x5.412.0) on disk rba:(0x5.442.0) > on disk scn: 0x0000.00115af9 01/18/2013 05:44:38 99c99
最后一部分是heartbeat心跳信息,每3秒更新一次用于验证实例的存活性:
< heartbeat: 805047415 mount id: 3142683107 --- > heartbeat: 805047509 mount id: 3142683107
通过以上分析可以清晰地看到增量检查点的实施过程,因为增量检查点可以连续进行,所以检查点rba可以比常规点更接近数据库的最后状态,从而在数据库的实例恢复中可以极大地减少恢复时间.而且,通过增量检查点,dbwr可以持续进行写出,从而避免了常规检查点出发的峰值,写入对于I/O的过度征用.
显而易见的是,增量检查点明显优于常规的完全检查点,所以在引入检查点队列之后,数据库正常情况下执行的都是增量检查点,从oracle8i开始,完全检查点仅仅在以下两种情况下出现:
alter system checkpoint;
shutdown(除了abort方式外)
log switch事件同样是触发的增量检查点,但是在log switch触发的检查点会促使数据文件头与控制文件信息的同步
log_checkpoints_to_alert参数
在数据库中,可以设置初始化参数log_checkpoints_to_alert为true,则数据库会将检查点的执行情况记入警告日志文件中,这个参数的初始值为false:
SQL> show parameter checkpoints_to NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ log_checkpoints_to_alert boolean FALSE SQL> alter system set log_checkpoints_to_alert=true; System altered.
当数据库执行各类检查点进,日志文件中会记录详细信息,以下是来自oracle 10g的警告日志文件中的信息摘录.注意以下信息中,共发生了两次检查点,触发条件都是log switch,在日志中,注意rba信息和检查点scn同时出现,这就是检查点队列的作用,log switch检查点的特别之处在于,需要同时在控制文件和数据文件头上标记检查点进度
[oracle@jingyong bdump]$ tail -20 alert_jingyong.log Fri Jan 18 06:12:00 2013 Beginning log switch checkpoint up to RBA [0x8.2.10], SCN: 1138121 Thread 1 advanced to log sequence 8 Current log# 2 seq# 8 mem# 0: /u01/app/oracle/product/10.2.0/oradata/jingyong/redo02.log Fri Jan 18 06:12:21 2013 Beginning log switch checkpoint up to RBA [0x9.2.10], SCN: 1138140 Thread 1 advanced to log sequence 9 Current log# 3 seq# 9 mem# 0: /u01/app/oracle/product/10.2.0/oradata/jingyong/redo03.log Fri Jan 18 06:17:12 2013 Completed checkpoint up to RBA [0x8.2.10], SCN: 1138121 Fri Jan 18 06:17:30 2013 Completed checkpoint up to RBA [0x9.2.10], SCN: 1138140
从以上信息还可以观察到,检查点的触发和检查点完成具有一定的时间间隔,这进一步说明,检查点仅仅是一个数据库事件,发生检查点时ckpt进程负责通知dbwr执行写出,但是检查点不会等待写出完成,它会在下一次触发时写出上一次成功完成的检查点信息.
在警告日志文件中,可能还会看到Incremental checkpoint的信息,这些信息和检查点的另外一个触发条件有关.为了保证检查点不会滞后整个日志文件,oracle限制最长的检查点跨度不超过最小日志大小的90%.所以数据库在运行过程中会根据log tail进行计算,主动触发增量检查点.