一、为什么要有检查点?
被修改过的块,在oracle中都被统称为脏块.所有的脏块被一个链表串起来,称做检查点队列.在buffer
cache中,每一个块都有一个buffer header 简称BH,在BH中有一个ckptq项,此项目中记录了指向检查点队
列上一个块和下一个块的指针.如果某一个块不在检查点队列中,他的ckptq项为空.通过ckptq项oracle将
所有的脏块串成了一个双向链表.这个双向链表就是检查点队列了.
1,只有脏块才会在检查点队列中,非脏块的ckptq为空.
2,当块首次被更改时,块会立即被加进检查点队列.如果检查点队列中的脏块再次被修改,并不会改变其在
检查点队列中的位置.
3,检查点队列中脏块的排列顺序:根据第2点,所有脏块按照首次被更改的时间的顺序排列.更准确点说:按
照块的lrba排列.
**什么是rba?lrba?hrba?
rba就是重做块地址,比如说,用户发出了一条update命令,更新了块A,块A现在变成了脏块,oracle会为他
生成一条重做记录.这条重做记录在重做日志文件中的位置就是rba(redo block address).过了一会儿,假
如:块A依然还是脏块,此时.用户又发出一条更新块A的命令,这又会生成一条重做记录.第一条更新命令对
应的重做记录的rba被称为块A的lrba(low rba),第二条更新命令对应的rba,被称为hrba(high rba).
其实,按照lrba来排列,就是按照块首次被修改的顺序来排列.
下面说说DBWR写脏块的方式,有了检查点队列之后,脏块按照首次变脏的时间顺序排列,DBWR每到一定的
时机,就会被触发,沿着检查点队列的顺序刷新脏块,具体在oracle中有几个参数用来确定检查点队列的长
度.另有一个CKPT进程,会监控着检查点队列的长度,当检查点队列的长度达到一定限制时,CKPT会通知DBWR
写脏块.CKPT会根据参数的设置和I/O的速度以及繁忙程度,计算出来一个Target rba(目标rba),DBWR会沿
着检查点队列,将所有Target rba之前的脏块刷新到磁盘.当CKPT通知完DBWR Target rba后,CKPT的任务就
结束了.他并不会等待DBWR写完所有的Target rba之前的脏块.通知DBWR写脏块,这是CKPT的任务之一,CKPT
另有一个任务,就是每3秒,检测一次DBWR的写进度.检查点队列最前面的块被称为检查点位置.DBWR是沿着
检查点队列写脏块的,CKPT每3秒钟查看一下DBWR沿检查点队列写到了哪里,并且将这个位置设置为检查点
位置.也就是说检查点位置之前的块,都是已被DBWR刷新到磁盘上的块.这个3秒一次检查DBWR进度的工作,
也是CKPT的一个重要的任务.CKPT每3秒一次将检查点位置记录进控制文件,当然同时被记录进控制文件的
还有'心跳'等其他信息.CKPT每3秒一次的工作和CKPT定期触发DBWR,这两项操作合一起被称为--增量检查
点.
下面的就是CKPT每3秒写进控制文件的信息
SQL> alter session set events 'immediate trace name controlf level 8';
会话已更改。
具体内容如下:
***************************************************************************
CHECKPOINT PROGRESS RECORDS
***************************************************************************
(size = 8180, compat size = 8180, section max = 11, section in-use = 0,
last-recid= 0, old-recno = 0, last-recno = 0)
(extent = 1, blkno = 2, numrecs = 11)
THREAD #1 - status:0x2 flags:0x0 dirty:89
low cache rba0x2ad.908.0)[检查点位置] on disk rba0x2ad.d2f.0)[最后一条重做记录的rba]
on disk scn: 0x0000.00237745 03/02/2008 15:03:44[最后一条重做记录的scn]
resetlogs scn: 0x0000.0008297b 08/27/2007 09:51:58
heartbeat: 648318959[心跳] mount id: 1201288562
...
...
这里面的大多数信息可以通过x$kcccp中看到.
SQL> select CPDRT,CPLRBA_SEQ||'.'||CPLRBA_BNO||'.'||CPLRBA_BOF "Low
RBA",CPODR_SEQ||'.'||CPODR_BNO||'.'||CPODR_BOF "On disk RBA",CPODS,CPODT,CPHBT from x$kcccp;
CPDRT Low RBA On disk RBA CPODS CPODT CPHBT
---------- --------------- --------------- ---------------- -------------------- ----------
35 686.124.0 686.220.0 2325376 03/02/2008 15:18:54 648319278
说明:
CPDRT列是检查点队列中的脏块数目.
CPODS列是on disk rba的scn
CPODT列是on disk rba的时间戳
CPHBT列是心跳
检查点位置是是个rba,他指向着重做日志文件中的某个重做记录.在此位置前的重做记录,其对应的信息已
经被写进了数据文件,在此位置后的重做记录,所对应的是数据块,有可能还在内存中.如果发生了实例崩溃
,只需要在日志文件中找到检查点位置,从此处开始应用所有的重做日志文件,就完成了前滚操作.实例崩溃
后,再次启动数据库,oracle会到控制文件中读取low cache rba,这就是检查点位置.从此处开始应用重做
信息,应用到on disk rba处.on disk rba是磁盘中重做日志文件的最后一条重做记录的rba. 如果某条命
令的重做记录的rba高于on disk rba,那说明此重做记录还没有被写进日志文件中,崩溃发生时,他是不
可能被恢复的.on disk rba是oracle前滚操作的终点.on disk 顾名思义 就是'在磁盘上'的意思.比这个
更高的rba,都在log buffer中,还没有来的急被写进磁盘中的日志文件.所以是不能被用于恢复的.
下面假设一个实例恢复的例子:
Table表每行2000个字节,块大小8K,每块可容纳table的3行,按如下发布7条更新命令:
Update table set name=low(name) where id=1; ------块1
RBA :692.2998.10
Update table set name=low(name) where id=2; ------块1 RBA :692.3015.10
Update table set name=low(name) where id=4; ------块2 RBA :692.3024.10
Update table set name=low(name) where id=7; ------块3 RBA :692.3033.10
Update table set name=low(name) where id=3; ------块1 RBA :692.3102.10
Update table set name=low(name) where id=10; ------块4 RBA :692.3127.10
Update table set name=low(name) where id=13; ------块5 RBA :692.3136.10
上面七条更新命令后,每块状态为:
1号块 |
2号块 |
3号块 |
4号块 |
5号块 |
Lrba:692.2998.10 Hrba:692.3102.10 | Lrba:692.3024.10 Hrba:692.3024.10 | Lrba:692.3033.10 Hrba:692.3033.10 | Lrba:692.3127.10 Hrba:692.3127.10 | Lrba:692.3136.10 Hrba:692.3136.10 |
Lrba就是块首次变脏时的RBA,而Hrba,是最后一次改变块中信息,所对应的重做记录的RBA。1号块外被修改两次,Lrba和Hrba不同。2到5号块只被修改一次,Lrba和Hrba相同。
相应的重做记录有:
RBA:692.2998.10(第一条更新命令对应的重做记录)
|
|||
RBA:692.3015.10 (第二条更新命令对应的重做记录) | |||
RBA:692.3024.10 (第三条更新命令对应的重做记录) | |||
RBA:692.3033.10 (第四条更新命令对应的重做记录) | |||
RBA:692.3102.10 (第五条更新命令对应的重做记录) | |||
RBA:692.3127.10 (第六条更新命令对应的重做记录) | |||
RBA:692.3136.10 (第七条更新命令对应的重做记录) |
假如此时,1号块和2号块已经变得不脏,3、4、5号块仍是脏块,所有的脏块依Lrba顺序按列为检查点队列,其中检查点队列头(此处是3号块)的Lrba就是检查点位置,此处为692.3033.10。这个值被记录在控制文件中。如果发生了实例崩溃,Oracle将从控制文件中取出692.3033.10,到692号重做日志中,找到第3033块,从此处开始,应用所有的重做日志,直到重做日志文件的最未尾。而重做日志文件的最未尾重做记录的RBA,又叫On disk rba。从检查点位置处,应用重做记录到On disk rba处,这个过程就是前滚。
如下几个参数可以用来限制检查点队列的长度:
1,fast_start_io_target
该参数用于表示数据库发生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.
2,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废除了.
3,log_checkpoint_timeout
该参数用于表示检查点位置和重做日志尾之间的时间间隔,以秒为单位,默认情况下是1800秒,这个参数
实际上表示了脏块保持脏状态的最长时间.如果它被定为1800秒,没有脏块保持1800秒后,还是为脏.
设log_checkpoint_timeout 为1800秒
图1.gif
相比fast_start_mttr_target,它也是时间,但它的时间值表示完成恢复操作所需要的时间,即从最后的
检查点位置开始,应用所有日志直到日志末尾所需要的时间.而本参数表示从最后的检查点位置开始,到日
志末尾经过的时间.
在标准版中,本参数的最小值是900.
4,log_checkpoint_interval
该参数是表示检查点位置和重做日志末尾的块的数量.以OS表示.
5,90% OF SMALLEST REDO LOG
oracle内部事实上还将重做日志末尾前面90%的位置设为检查点位置,这不是一个参数,这是oracle内部
规定的一个触发增量检查点的事件.
上面这些条件,严格来说,并不是控制检查点什么时候发生,而是控制检查点队列中可以有多少个块.在前
4个参数中,9I中oracle推荐使用fast_start_mttr_target替代第一个 fast_start_io_target.
fast_start_mttr_target,log_checkpoint_timeout,log_checkpoint_interval和90% OF SMALLEST REDO
LOG 可以同时使用.考虑这样一种情况,如果上面的这些触发增量检查点的参数都被设置,并且在某一时刻,
这几个参数一起被触发,但他们指定的Target RBA位置可能不尽相同,oracle将离日志末尾最近的那个位置
认为检查点位置,如下图所示:
图2.gif
在这种情况下,将会把log_checkpoint_interval的位置定为下一增量检查点的Target RBA.
在9I后,对检查点频率,建议只设置fast_start_mttr_target.根据需要,也可以通过设置
log_checkpoint_timeout,设置一个脏块保持脏状态的最大时间,而其他两个参数
fast_start_io_target,log_checkpoint_interval建议不再使用.
oracle写脏块并不一定都从检查点队列中写.在v$sysstat视图中,有两项关于物理写的资料.physical
writes 和physical writes non checkpoint.也就是说,oracle将对脏块的写分为两类.一类是通过检查点
的写,一类是不通过检查点的写.我把它叫做检查点无关写.比如说:当表空间脱机时,会把隶属于该表空间
的所有脏块都写进数据文件,但是不会发生检查点,这个写就是检查点无关写. 还有其他的情况会发生检查
点无关写,我会在以后的实验中介绍.
一个小实验,证明下检查点无关写:为了避免检查点对实验的影响,将检查点的发生频率设置的低一些.
命令如下:
alter system set fast_start_mttr_target=0;
alter system set log_checkpoint_timeout=3600;
步骤一:在实验前先观察下当前物理写的值:
SQL> select * from v$sysstat where name='physical writes non checkpoint';
STATISTIC# NAME CLASS VALUE STAT_ID
---------- ------------------------------ ---------- ---------- ----------
67 physical writes non checkpoint 8 3738 2602029796
步骤二:随便开始一个事务
SQL> update jj_10 set name='aa' where id=20;
已更新 1 行。
步骤三:把步骤二中的表脱机:
SQL> alter tablespace jj_ts_1 offline;
表空间已更改。
步骤四:此时再去查看资料视图:
SQL> select * from v$sysstat where name='physical writes non checkpoint';
STATISTIC# NAME CLASS VALUE STAT_ID
---------- ------------------------------ ---------- ---------- ----------
67 physical writes non checkpoint 8 3759 2602029796
**比较后发现,检查点无关写从3738增加到3759.
为了观察到通过检查点队列的写,把检查点频率调的高一点:
alter system set log_checkpoint_timeout=10;
步骤一:
SQL> select * from v$sysstat where name='physical writes' or name='physical writes non
checkpoint';
STATISTIC# NAME CLASS VALUE STAT_ID
---------- ------------------------------ ---------- ---------- ----------
62 physical writes 8 5822 1190468109
67 physical writes non checkpoint 8 3829 2602029796
用physical writes减去physical writes non checkpoint所得到的结果,将近似于通过检查点队列的写.
为什么说近似于呢?因为oracle内部会有很多写,比如说控制文件的写操作,也会被记录进physical writes
资料.
步骤二:发布更新命令
SQL> update jj_10 set name='aa' where id=20;
已更新 1 行。
步骤三:观察块是否变的不脏.
SQL> select dirty,status from v$bh where file#=7 and block#=406 and status='xcur';
D STATUS
- -------
N xcur
步骤四:在块变的不脏后,马上查看资料视图.
SQL> select * from v$sysstat where name='physical writes' or name='physical writes non
checkpoint';
STATISTIC# NAME CLASS VALUE STAT_ID
---------- ------------------------------ ---------- ---------- ----------
62 physical writes 8 5851 1190468109
67 physical writes non checkpoint 8 3832 2602029796
**可以看到检查点无关写多了3个字节,这3个字节和我们的更新声明没有关系.我们的更新声明更新了几十
个字节.这3个字节应该是属于oracle内部的一些写操作,我们的更新声明,所产生的脏块,是通过检查点队
列写出的.physical writes 多了很多。