揭示检查点的秘密
oracle 8以前都是完全检查点,每次触发,则记录当前数据库SCN号,并将内存里所有截至到该SCN号的脏数据块都刷新到数据文件里。从而同步数据文件。
随着内存越来越便宜,以及控制实例恢复的需要,完全检查点已经不适合实际情况了。于是从oracle8开始,oracle引入了增量检查点,通过增量检查点来减少实例恢复的时间。为此,oracle引入了checkpointqueue,按照数据块第一次被修改的先后顺序,将脏数据块挂到该队列上。同时还在该队列上记录了每个脏数据块第一次被修改时产生的redoentry的地址,这叫LRBA。
增量检查点的启动时机由以下参数控制:
·fast_start_mttr_target:应用从CKPT position到最后一个redo entry之间所有redo entry所花的时间。也就是实例前滚所花费的时间。
·LOG_CHECKPOINT_INTERVAL:如果自从上一次检查点启动以来所累积的日志块个数达到该参数,则触发增量检查点,从而触发DBWn写脏块。
·LOG_CHECKPOINT_TIMEOUT:两次检查点启动的时间间隔。实际也就是控制脏块在内存里待多长时间的参数。
·90%*(SUM(log_size) – MAX(log_size)):如果产生的redo块的量达到该值,则启动增量检查点。
只要满足上面条件中的任何一个,就会启动增量检查点。增量检查点一旦启动,则会记录当前CKPT SCN。
然后通知DBWn进程去写checkpoint queue上所挂载的脏块,至于写到哪个脏块为止,则不超过到当前CKPT SCN。
DBWn进程是批量写脏块的,脏块个数不到一定程度,DBWn不会真的去写。而每次写多少个脏块的个数则由一个隐藏参数控制。
每隔3秒钟(这叫heartbeat,心跳),“增量检查点”也还会启动一次,只不过这时增量检查点启动只是查看一下checkpointqueue,将该队列上的第一个脏块所对应的redo block的地址写入控制文件,也就是将CKPTPosition写入控制文件,并不会触发DBWn进程写脏块。所以是打引号的增量检查点。
有关检查点更详细的信息,google、baidu等都可以搜到,这里不再赘述。
在10g版本中,完全检查点也存在,其触发时机:
1、发出命令:alter system checkpoint;
2、正常关闭数据库。
有关检查点(包括完全检查点和增量检查点)的视图主要有两个:
1、v$instance_recovery。其中主要的字段包括:
·actual_redo_blks:最近一次检查点所对应的要写入的脏块所对应的redo block到写入了日志文件的最后一个redo block之间的redo blocks数量;
·target_redo_blks:所有检查点条件中最小的条件所导致的在内存里的redo blocks数量;
·log_file_size_redo_blks:其字段值为90%*(SUM(log_size) – MAX(log_size))的计算结果。
·log_chkpt_timeout_redo_blks:由log_checkpoint_timeout参数所得到的redo blocks数量。
·target_mttr:由fast_start_mttr_target参数所计算出来的实例恢复所需要的时间。
·estimated_mttr:根据当前最后一次检查点与日志尾所差的redo blocks数量估算出来的实例恢复所需要的时间。
2、X$KCCCP。该内存表的命名含义为:
[K]ernel [C]ache [C]ontrolfile management [c]heckpoint [p]rogress。
其中主要的字段包括:
·CPLRBA_SEQ:最后一次增量检查点对应LRBA的第一部分--日志序列号;
·CPLRBA_BNO:最后一次增量检查点对应LRBA的第二部分--日志块数;
·CPLRBA_BOF:最后一次增量检查点对应LRBA的第三部分--日志偏移量;
·CPODR_SEQ:已写入日志文件的最后一个RBA的第一部分--日志序列号;
·CPODR_BNO:已写入日志文件的最后一个RBA的第二部分--日志块数;
·CPODR_BOF:已写入日志文件的最后一个RBA的第二部分--日志偏移量;
·CPHBT:每三秒更新一次,并写入控制文件的heartbeat部分。表示这时的SCN号。
换句话所,当进行实例恢复时,需要应用的redo entry从CPLRBA_*到CPODR_*为止。
一、完全检查点
SQL> select CPLRBA_SEQ as cur_seq#,
2 CPLRBA_BNO as cur_blk#,
3 CPODR_SEQ as stop_seq#,
4 CPODR_BNO as stop_blk#
5 from x$kcccp
6 where CPODR_SEQ>0;
CUR_SEQ# CUR_BLK# STOP_SEQ# STOP_BLK#
---------- ---------- ---------- ----------
37 97550 37 97778
SQL> select count(*) from v$bh where dirty='Y';
COUNT(*)
----------
63
SQL> alter system checkpoint;
系统已更改。
SQL> select count(*) from v$bh where dirty='Y';
COUNT(*)
----------
0
很明显,发出该命令以后,内存里所有脏块都被写入了数据文件,于是内存里就没有脏块了。
打开跟踪文件,可以看到:
Beginning global checkpoint up to RBA [0x25.17df2.10], SCN: 662364
Completed checkpoint up to RBA [0x25.17df2.10], SCN: 662364
SQL> select to_number('25','xx') from dual;
TO_NUMBER('25','XX')
--------------------
37
SQL> select to_number('17df2','xxxxxxxx') from dual;
TO_NUMBER('17DF2','XXXXXXXX')
-----------------------------
97778
将其与x$kcccp的查询结果比较一下,也可以看到,在完全检查点中,截至写入数据文件的脏块所对应的redo entry对应到STOP_SEQ#和STOP_BLK#所指明的redo entry为止。
二、增量检查点
测试增量检查点,主要通过v$instance_recovery视图。
我们通常不会去修改log_checkpoint和fast_start参数,那些参数的默认值都设置的很大,所以还没到那些参数所指定的上限的时候,90%这个条件就生效了。因为增量检查点在任何一个条件满足就会被触发。
按照前面的公式:90%*(SUM(log_size) – MAX(log_size)),我们来计算日志量是多大。
SQL> select 0.90*(sum(bytes)/1024/1024-max(bytes/1024/1024)) from v$log;
0.90*(SUM(BYTES)/1024/1024-MAX(BYTES/1024/1024))
------------------------------------------------
180
可以看到,只要日志量达到180M,就会发生增量检查点。然后来验证:
SQL> select actual_redo_blks,
2 target_redo_blks,
3 LOG_FILE_SIZE_REDO_BLKS as "90%_blks",
4 LOG_CHKPT_TIMEOUT_REDO_BLKS as timeout_blks,
5 target_mttr,
6 estimated_mttr
7 from v$instance_recovery;
ACTUAL_REDO_BLKS TARGET_REDO_BLKS 90%_blks TIMEOUT_BLKS TARGET_MTTR ESTIMATED_MTTR
---------------- ---------------- ---------- ------------ ----------- --------------
89 368640 368640 0 27
这里368640表示redo block的个数,至于每个redo block多大,则通常为500 bytes。也可以通过下面的sql来验证:
SQL> select distinct lebsz as redo_block_size from x$kccle where lebsz>0;
REDO_BLOCK_SIZE
---------------
512
于是,我们得到:368640*512/1024/1024=180M。很明显,与前面的公式吻合。
同时,我们看到当前内存里的脏块所对应的redo block有89个。而其他控制增量检查点的参数都保持缺省值:
SQL> show parameter log_checkpoint
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
log_checkpoint_interval integer 0
log_checkpoint_timeout integer 1800
log_checkpoints_to_alert boolean TRUE
SQL> show parameter fast_start
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
fast_start_io_target integer 0
fast_start_mttr_target integer 0
fast_start_parallel_rollback string LOW
假设当前所在的session为sess #1。然后再启动一个session,假设其session为sess #2。发出大量的DML语句。
SQL> connect hr/hr
已连接。
SQL> create table test as select * from dba_objects;
表已创建。
回到sess #1,查看v$instance_recovery:
SQL> select actual_redo_blks,
2 target_redo_blks,
3 LOG_FILE_SIZE_REDO_BLKS as "90%_blks",
4 LOG_CHKPT_TIMEOUT_REDO_BLKS as timeout_blks,
5 target_mttr,
6 estimated_mttr
7 from v$instance_recovery;
ACTUAL_REDO_BLKS TARGET_REDO_BLKS 90%_blks TIMEOUT_BLKS TARGET_MTTR ESTIMATED_MTTR
---------------- ---------------- ---------- ------------ ----------- --------------
308 368640 368640 0 22
可以看到,只产生了大约300多个redo block。于是回到sess #2,不断的做insert+delete:
SQL> insert into test select * from test;
已创建50320行。
SQL> commit;
提交完成。
SQL> delete test;
已删除100640行。
SQL> commit;
提交完成。
大概做3、4次左右,回到sess #1,查看v$instance_recovery。
SQL> select actual_redo_blks,
2 target_redo_blks,
3 LOG_FILE_SIZE_REDO_BLKS as "90%_blks",
4 LOG_CHKPT_TIMEOUT_REDO_BLKS as timeout_blks,
5 target_mttr,
6 estimated_mttr
7 from v$instance_recovery;
ACTUAL_REDO_BLKS TARGET_REDO_BLKS 90%_blks TIMEOUT_BLKS TARGET_MTTR ESTIMATED_MTTR
---------------- ---------------- ---------- ------------ ----------- --------------
341604 368640 368640 0 105
这时内存里的脏数据块所对应的redo block已经接近上限了。于是再次回到sess #2里做DML,结束以后回到sess #1查看。
SQL> select actual_redo_blks,
2 target_redo_blks,
3 LOG_FILE_SIZE_REDO_BLKS as "90%_blks",
4 LOG_CHKPT_TIMEOUT_REDO_BLKS as timeout_blks,
5 target_mttr,
6 estimated_mttr
7 from v$instance_recovery;
ACTUAL_REDO_BLKS TARGET_REDO_BLKS 90%_blks TIMEOUT_BLKS TARGET_MTTR ESTIMATED_MTTR
---------------- ---------------- ---------- ------------ ----------- --------------
378157 368640 368640 0 184
已经超过上限,于是触发增量检查点。这时可以去查看alert log:
Sun Nov 23 12:47:47 2008
Beginning log switch checkpoint up to RBA [0x26.2.10], SCN: 667801
Thread 1 advanced to log sequence 38
Current log# 6 seq# 38 mem# 0: C:\ORACLE\PRODUCT\10.2.0\ORADATA\ORCL\REDO06.LOG
Sun Nov 23 12:48:47 2008
Beginning log switch checkpoint up to RBA [0x27.2.10], SCN: 677714
Thread 1 advanced to log sequence 39
Current log# 4 seq# 39 mem# 0: C:\ORACLE\PRODUCT\10.2.0\ORADATA\ORCL\REDO04.LOG
很明显,可以看到触发了2次增量检查点,第一次增量检查点要写到[0x26.2.10]为止,第二次要写到[0x27.2.10]为止。同时这两次增量检查点都还没有完成,因为在checkpoint queue上,截至到该RBA的redoentry所对应的脏块还没有被写入数据文件。增量检查点只是通知DBWn写到这个位置并不等DBWn写完就返回了。这里的26对应到十进制就是:2*16+6=38,也就是写到序列号为38为止。而27则对应到39号日志文件。
SQL> select CPLRBA_SEQ as cur_seq#,
2 CPLRBA_BNO as cur_blk#,
3 CPODR_SEQ as stop_seq#,
4 CPODR_BNO as stop_blk#
5 from x$kcccp
6 where CPODR_SEQ>0;
CUR_SEQ# CUR_BLK# STOP_SEQ# STOP_BLK#
---------- ---------- ---------- ----------
37 108095 39 85497
SQL> select group#,sequence#,status from v$log;
GROUP# SEQUENCE# STATUS
---------- ---------- ----------------
4 39 CURRENT
5 37 ACTIVE
6 38 ACTIVE
可以看到37、38这两个日志所对应的脏块还没有被写入数据文件,所以其状态为active。
SQL> select actual_redo_blks,
2 target_redo_blks,
3 LOG_FILE_SIZE_REDO_BLKS as "90%_blks",
4 LOG_CHKPT_TIMEOUT_REDO_BLKS as timeout_blks,
5 target_mttr,
6 estimated_mttr
7 from v$instance_recovery;
ACTUAL_REDO_BLKS TARGET_REDO_BLKS 90%_blks TIMEOUT_BLKS TARGET_MTTR ESTIMATED_MTTR
---------------- ---------------- ---------- ------------ ----------- --------------
368599 368640 368640 0 161
同时也发现,内存里的脏块所对应的redo block个数从378157下降到了368599,也就是只减少了很少的redoblock,换句花说,这时DBWn进程只写了很少的几个脏块。因为写的脏块个数只需要满足不触发增量检查点即可,这里的条件也就是内存里的脏块所对应的redo block不要超过368640即可。
然后再次的在sess #2里做DML。然后回到sess #1。
SQL> select CPLRBA_SEQ as cur_seq#,
2 CPLRBA_BNO as cur_blk#,
3 CPODR_SEQ as stop_seq#,
4 CPODR_BNO as stop_blk#
5 from x$kcccp
6 where CPODR_SEQ>0;
CUR_SEQ# CUR_BLK# STOP_SEQ# STOP_BLK#
---------- ---------- ---------- ----------
38 22297 39 195257
可以看到发生变化了,checkpoint position从37号日志向前推进到了38号日志。也就是说,完成了一次检查点。
马上去看alert log,发现下面这一段:
Thread 1 advanced to log sequence 39
Current log# 4 seq# 39 mem# 0: C:\ORACLE\PRODUCT\10.2.0\ORADATA\ORCL\REDO04.LOG
Sun Nov 23 13:00:56 2008
Completed checkpoint up to RBA [0x26.2.10], SCN: 667801
Sun Nov 23 13:01:57 2008
Beginning log switch checkpoint up to RBA [0x28.2.10], SCN: 685552
Thread 1 advanced to log sequence 40
Current log# 5 seq# 40 mem# 0: C:\ORACLE\PRODUCT\10.2.0\ORADATA\ORCL\REDO05.LOG
很明显,第一次增量检查点结束了,也就是说,前面active的37号日志文件里的redo entry所对应的脏块已经被
写入了数据文件,其状态肯定变为inactive。同时第二次增量检查点还没有结束,同时又启动了第三次增量检查点。
SQL> select group#,sequence#,status from v$log;
GROUP# SEQUENCE# STATUS
---------- ---------- ----------------
4 39 CURRENT
5 37 INACTIVE
6 38 ACTIVE
SQL> select CPLRBA_SEQ as cur_seq#,
2 CPLRBA_BNO as cur_blk#,
3 CPODR_SEQ as stop_seq#,
4 CPODR_BNO as stop_blk#
5 from x$kcccp
6 where CPODR_SEQ>0;
CUR_SEQ# CUR_BLK# STOP_SEQ# STOP_BLK#
---------- ---------- ---------- ----------
38 22297 39 195257
整个过程是很容易理解的。这里可以很容易得出结论:通常所说的logfile switch 触发检查点,实际上就是触发增量检查点而不是完全检查点,并标记要写到哪个脏块为止。同时不等DBWn写完这些脏块就返回。当DBWn写完所标记的脏块以后,该增量检查点结束。
然后来修改log_checkpoint_timeout参数,从而调整增量检查点的频率。该参数表示脏数据块留在内存里的时间长度,一旦超过该时间,就会触发增量检查点,从而确定要写到的redo entry,从而确定了要写到的脏块地址,然后触发DBWn进程写脏块。将该参数设置为60秒。
SQL> alter system set log_checkpoint_timeout=60;
系统已更改。
SQL> select actual_redo_blks,
2 target_redo_blks,
3 LOG_FILE_SIZE_REDO_BLKS as "90%_blks",
4 LOG_CHKPT_TIMEOUT_REDO_BLKS as timeout_blks,
5 target_mttr,
6 estimated_mttr
7 from v$instance_recovery;
ACTUAL_REDO_BLKS TARGET_REDO_BLKS 90%_blks TIMEOUT_BLKS TARGET_MTTR ESTIMATED_MTTR
---------------- ---------------- ---------- ------------ ----------- --------------
6 40 368640 40 0 23
可以看到,修改为60秒以后,TARGET_REDO_BLKS为40,表示内存里只能容纳40个redo block所保护的脏数据块。然后在sess #2里进行DML以后,回到sess #1里查看相关视图。
SQL> select actual_redo_blks,
2 target_redo_blks,
3 LOG_FILE_SIZE_REDO_BLKS as "90%_blks",
4 LOG_CHKPT_TIMEOUT_REDO_BLKS as timeout_blks,
5 target_mttr,
6 estimated_mttr
7 from v$instance_recovery;
ACTUAL_REDO_BLKS TARGET_REDO_BLKS 90%_blks TIMEOUT_BLKS TARGET_MTTR ESTIMATED_MTTR
---------------- ---------------- ---------- ------------ ----------- --------------
22791 22791 368640 22791 0 25
等待1分钟左右,再次查询该视图,会发现其结果为:
SQL> select actual_redo_blks,
2 target_redo_blks,
3 LOG_FILE_SIZE_REDO_BLKS as "90%_blks",
4 LOG_CHKPT_TIMEOUT_REDO_BLKS as timeout_blks,
5 target_mttr,
6 estimated_mttr
7 from v$instance_recovery;
ACTUAL_REDO_BLKS TARGET_REDO_BLKS 90%_blks TIMEOUT_BLKS TARGET_MTTR ESTIMATED_MTTR
---------------- ---------------- ---------- ------------ ----------- --------------
73 75 368640 75 0 22
很明显,1分钟以后,大部分脏块被增量检查点触发DBWn写入数据文件。
然后来测试fast_start_mttr_target参数。
SQL> alter system set fast_start_mttr_target=40;
系统已更改。
SQL> show parameter log_checkpoint
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
log_checkpoint_interval integer 0
log_checkpoint_timeout integer 0
log_checkpoints_to_alert boolean TRUE
SQL> show parameter fast_start
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
fast_start_io_target integer 0
fast_start_mttr_target integer 40
fast_start_parallel_rollback string LOW
设置完fast_start_mttr_target=40以后,立刻查看相关视图:
SQL> select actual_redo_blks,
2 target_redo_blks,
3 LOG_FILE_SIZE_REDO_BLKS as "90%_blks",
4 LOG_CHKPT_TIMEOUT_REDO_BLKS as timeout_blks,
5 target_mttr,
6 estimated_mttr
7 from v$instance_recovery;
ACTUAL_REDO_BLKS TARGET_REDO_BLKS 90%_blks TIMEOUT_BLKS TARGET_MTTR ESTIMATED_MTTR
---------------- ---------------- ---------- ------------ ----------- --------------
104 368640 368640 40 22
可以看到,TARGET_MTTR就是我们设置的40秒,而ESTIMATED_MTTR则表示由于当前内存里脏块很少,因此如果当前实例崩溃,则只需要22秒就能完成实例的恢复。
在sess #2里进行DML以后,马上到sess #1里查看相关视图。
SQL> select actual_redo_blks,
2 target_redo_blks,
3 LOG_FILE_SIZE_REDO_BLKS as "90%_blks",
4 LOG_CHKPT_TIMEOUT_REDO_BLKS as timeout_blks,
5 target_mttr,
6 estimated_mttr
7 from v$instance_recovery;
ACTUAL_REDO_BLKS TARGET_REDO_BLKS 90%_blks TIMEOUT_BLKS TARGET_MTTR ESTIMATED_MTTR
---------------- ---------------- ---------- ------------ ----------- --------------
67141 368640 368640 40 42
可以看到这时sess #2产生了67141个redo block,这将触发增量检查点。然后我们间隔很短的时间,比如30秒以后再次查询该视图。
SQL> select actual_redo_blks,
2 target_redo_blks,
3 LOG_FILE_SIZE_REDO_BLKS as "90%_blks",
4 LOG_CHKPT_TIMEOUT_REDO_BLKS as timeout_blks,
5 target_mttr,
6 estimated_mttr
7 from v$instance_recovery;
ACTUAL_REDO_BLKS TARGET_REDO_BLKS 90%_blks TIMEOUT_BLKS TARGET_MTTR ESTIMATED_MTTR
---------------- ---------------- ---------- ------------ ----------- --------------
132 368640 368640 40 42
会发现,由于我们设置了40秒的实例恢复时间,因此马上就完成了增量检查点,减少了内存里脏数据块的个数。