热衷技术,热爱交流
分类: Oracle
2013-05-05 19:30:47
Oracle修改数据的时候,首先把数据读入内存(data buffer),在内存里面修改,修改的同时,记录重做日志信息(重做日志实际也有log buffer,只是日志写入相对是很频繁的,如果log buffer写入日志文件之前就断电,这些未写入重做日志的数据也是无法恢复的,日志部分这里不做更多研究了)。那么,既然数据在 data buffer中被修改了,它最终是要写入数据文件的,那么oracle是怎么往数据文件写东西的呢?这里,就是检查点的功劳了(实际上,数据写入文件不一定要检查点,但是出于优化恢复的需要,检查点是必不可少的)。
检查点就是一个数据库事件,它由后台进程CKPT触发,通知DBWR进程把脏数据写入磁盘,同时通知CKPT进程更新控制文件和数据文件头信息。一般来讲,检查点更新比重做日志更新慢。对于异常断电,如果检查点越逼近日志文件的最后更新,那么需要恢复的数据就会越少。当然,检查点发生太过频繁,势必会产生过多I/O,影响系统性能。那么,ORACLE要做的就是在考虑让检查点逼近日志文件的最后更新的同时,兼顾数据库的性能。
一、 常规检查点:
常规检查点又叫完全检查点,oracle 8之前用的就是这项技术。常规检查点在执行检查点的时候,把全部的data buffer写入数据文件,工作过程如下:
第一步、 开始checkpoint事件,记录当前scn和当前的checkpoint rba。
这里谈一下rba:
rba(redo bytes address),即重做日志块地址(脏块对应的重做日志记录),它包括以下几种RBA:
(1) LRBA,一个块第一次被列入脏列表时候对应的RBA
(2)HRBA,一个脏块最后修改时候对应的RBA
(3)checkpoint RBA,表示检查点发生时当前最后的的重做日志块的地址,上一个checkpoint RBA到当前的checkpoint RBA之间的日志对应的的buffer cache中的脏块接下来将会被写入到数据文件当中去(类似增量检查点的target ,可以作为前滚恢复的起点)。
(4)on disk rba,表示disk中redo log中的最后一条redo recode记录,当实例crash时候,他就是数据库前滚的终点
rba分为三部分,第一部分表示日志文件序列号,第二部分表示日志文件块编号,第三部分表示重做日志记录在日志文件块中的起始偏移。例如rba:(0x12.12392.0),表示日志文件序列号是21,日志文件块编号是ox12392,重做记录在块中偏移值为0。如下所示,如果执行日志切换操作,就会看到当前使用的日志序列号为切换前使用的前那个日志文件的序列号加1,而且当前使用的日志文件序列号总是最大的。
SYS AS SYSDBA >select group#,sequence#,members,status from v$log;
GROUP#| SEQUENCE#|
MEMBERS|STATUS
----------|----------|----------|----------------
1| 16|
1|INACTIVE
2| 17|
1|INACTIVE
3| 18|
1|CURRENT
SYS AS SYSDBA
>alter system switch logfile;
SYS AS SYSDBA
>select group#,sequence#,members,status from v$log;
GROUP#| SEQUENCE#|
MEMBERS|STATUS
--- ------|----------|----------|----------------
1| 19|
1|CURRENT
2| 17|
1|INACTIVE
3| 18|
1|INACTIVE
第二步、 把checkpoint rba之前的buffer cache数据全部写入数据文件
第三步、脏块写完后,CKPUT进程用写完那一刻的scn和写完的那个checkpoint rba更新控制文件和数据文件头信息。也就是说,常规检查点的scn实际是要滞后于发生检查点时候的那个scn的。一旦在写入过程中发生掉电,重启后将要进行回滚恢复?
现在做一个常规检查点实验:
1. 执行下面命令:把检查点日志输入到告警日志
alter system set log_checkpoints_to_alert=TRUE SCOPE=MEMORY;
2. 执行检查点:
SYS AS SYSDBA >alter system checkpoint;
3. 查看告警文件
oracle[~/diag/rdbms/hx/hx/trace]$tail -f alert_hx.log
Wed May 08 21:58:28 2013
Beginning global checkpoint up to RBA [0x20.4eae.10], SCN:
4514520
Completed checkpoint up to RBA [0x20.4eae.10], SCN: 4514520
4. 对比x$kccrt表:
SYS AS SYSDBA >SELECT
rtckp_rba_seq,rtckp_rba_bno,rtckp_rba_bof,rtckp_scn FROM x$kccrt;
RTCKP_RBA_SEQ|RTCKP_RBA_BNO|RTCKP_RBA_BOF|RTCKP_SC
-------------|-------------|-------------|----------------
32| 20142|
16|4514520
5. 更新完成后,转储数据文件头:
alter session set events 'immediate trace name file_hdrs level 10';
查看内容:
Checkpointed at scn:
0x0000.0044e2d8 05/08/2013 21:58:28
thread:1 rba:(0x20.4eae.10)
SYS AS SYSDBA >select to_number('44e2d8','xxxxxx') from dual;
TO_NUMBER('44E2D8','XXXXXX')
----------------------------
4514520
其他可以触发常规检查点的操作:
Shutdown immediate
上面的由于是全局性的常规检查点,所以又叫线程检查点
下面几种局部的常规检查点可看作是表空间或者数据文件检查点
ALTER DATABASE BEGIN BACKUP 语句;
making a tablespace read-only;
taking it offline normal;
shrinking a datafile;
executing ALTER TABLESPACE BEGIN BACKUP;
二、增量检查点:
增量检查点是8i以后出现的,它的目的就是为了在保证系统I/O性能的前提下减少数据库恢复的时间。增量检查点和常规检查点的区别:
1. 不用一次写出全部data buffer,而是根据一定算法分批写出;
2. 写并不更新数据文件和控制文件的检查点信息,只是把当前最低的rba(low cache rba)写入控制文件;
上面的最低rba是什么意思呢,这要从8i以后的检查点队列(CKPTQ)说起
数据文件中的块被读入内存,第一次变成脏块后,会生成一个对应重做日志的RBA,这个RBA就是前面说的LRBA,那么每个脏块都会有这个LRBA。这些LRBA就构成了检查点队列。
检查点队列存于与buffer Header中,可以转存下buffer cache信息,查看检查点队列(转储过程很占cpu资源,慎用!!)。
SYS AS SYSDBA >alter session set events 'immediate trace name buffers level 10';
随便看一个buffer块的头部信息(需要是dirty buffer)
BH (0x63fe8ed8) file#: 7 rdba: 0x01c002f2 (7/754) class: 1 ba: 0x63dca000
……………………
ckptq: [0x633f8bc8,0x627e6d98] fileq: [0x633f5eb8,0x643f4498] objq: [0x627e6ea8,0x627e88c8] objaq: [0x627e6eb8,0x62
7e88d8]
……………………
LRBA: [0x15.101f0.0] LSCN: [0x0.440063] HSCN: [0x0.440063] HSUB: [1]
buffer tsn: 5 rdba: 0x01c002f2 (7/754)
scn: 0x0000.00440063 seq: 0x01 flg: 0x00 tail: 0x00630601
frmt: 0x02 chkval: 0x0000 type: 0x06=trans data
这个头部信息ckptq: [0x633f8bc8,0x627e6d98]形成了检查点队列链表上的一个节点,这个节点上的0x633f8bc8,0x627e6d98]分别表示前一个块和后一个块0x633f8bc8à0x627e6d98。如果我们看看其他的buffer头信息,就可以形成一个完整的队列结构。队列先进先出,所以最先进入这个队列的脏块会被优先写出。
看了这个结构,就可以理解最低rba的意思了。比如说,某个时刻,一个检查点队列如下aaaa-->bbbb-->cccc-->dddd-->eeee,一次增量写出了aaaa,bbbb,cccc对应的脏块,那么写完后, dddd代表的脏块的lrba就会成为最低rba,表示下次增量写出要从这里开始了。
那么,oracle是怎么判断我一次增量写出需要写出aaaa,bbbb,cccc这三个块,然后让dddd的lrba成为最低rba呢?
原来,CKPT进程一直在监视检查点队列,当检查点队列达到一定值的时候,他会根据系统运行状况计算出一个target rba,通知,dbwr从最低rdba开始,一直写到这个target rba。然后,CKPT会预计target rba后面的rba可以成为最低rdba。但是,CKPT通知完了以后,任务没没有结束,他会每隔三秒看看dbwr写到哪里了。比如说,隔了3s,dbwr写完了bbbb,那么CKPT就会更新控制文件中的最低rba为cccc。最后,dbwr按照CKPT的要求写完了,CKPT就确定,最低RBA是dddd了。其实ckpt不在计算后就马上更新控制文件的最低RBA是合理的。因为有可能在做增量检查点的时候,系统突然挂了,那么系统重启后,虽然控制文件已经是那个预期的最低rdba,但是实际上,dbwr还没写到呢!!这里需要注意的是:在一次增量检查过程中,控制文件中的最低rba会每隔3s更新一次的。
做个实验,看看增量检查点的过程:
首先,显式设置增量检查点发生时间为60s;
SYS AS SYSDBA >alter system set log_checkpoint_timeout=60 scope=memory;
看看告警日志信息:
oracle[~/diag/rdbms/hx/hx/trace]$tail -f alert_hx.log
Wed May 08 22:33:57 2013
Incremental checkpoint up to RBA [0x20.1417e.0], current log tail at RBA [0x20.14191.0]
告警日志显示,当前增量检查点将要增量写到RBA [0x15.12391.0]。
看看控制文件内容:
SYS AS SYSDBA >alter session set events 'immediate trace name controlf level 8';
***************************************************************************
CHECKPOINT PROGRESS RECORDS
***************************************************************************
(size = 8180, compat size = 8180, section max = 4, section in-use = 0,
last-recid= 0, old-recno = 0, last-recno = 0)
(extent = 1, blkno = 2, numrecs = 4)
THREAD #1 - status:0x2 flags:0x0 dirty:1344
low cache rba:(0x20.14192.0) on disk rba:(0x20.15b42.0)
on disk scn: 0x0000.004535e3 05/08/2013 22:34:33
resetlogs scn: 0x0000.003cef48 04/26/2013 21:26:02
heartbeat: 814880295 mount id: 771284362
再看看表记录:
SYS AS SYSDBA >start kcccp;
CPLRBA_SEQ|CPLRBA_BNO|CPLRBA_BOF| CPODR_SEQ| CPODR_BNO| CPODR_BOF|CPODS |CPDRT| CPHBT
----------|----------|----------|----------|----------|----------|----------------|----------|----------
32| 82322| 0| 32| 88432| 0|4535769 | 1734| 814880289
控制文件显示,当前还有脏块数量为1344,最低rba为rba:( 0x20.14192.0),正好是告警日志那个数加1,on disk rba表示磁盘上最后一条重做日志的RBA。比它再高的,那就只能在log buffer里面了。需要走注意的是,一旦脏块数量变为0,那么low cache rba就会变成全f,表示下次不用再写出buffer了。
三、日志切换检查点
日志切换检查点是一种特殊的检查点:考虑到系统负载,日志切换时候,oracle并不立即把之前的重做日志保护的脏数据写入数据文件:
实验如下:
刚开始:只有3号日志文件是当前使用的,其他两个文件不包含当前恢复需要的重做日志
查看重做日志文件:
SYS AS SYSDBA > select GROUP#,THREAD#,SEQUENCE#,STATUS ,FIRST_CHANGE#,NEXT_CHANGE# from v$log;
GROUP#| THREAD#| SEQUENCE#|STATUS |FIRST_CHANGE#|NEXT_CHANGE#
----------|----------|----------|----------------|-------------|------------
1| 1| 37|INACTIVE | 4537416| 4537426
2| 1| 38|INACTIVE | 4537426| 4537434
3| 1| 39|CURRENT | 4537434| 2.8147E+14
SYS AS SYSDBA > SELECT CPLRBA_SEQ,CPLRBA_BNO,CPLRBA_BOF,CPODR_SEQ,CPODR_BNO,CPODR_BOF,CPODS,CPDRT,CPHBT FROM x$kcccp;
CPLRBA_SEQ|CPLRBA_BNO|CPLRBA_BOF| CPODR_SEQ| CPODR_BNO| CPODR_BOF|CPODS | CPDRT| CPHBT
----------|----------|----------|----------|----------|----------|----------------|----------|----------
4294967295|4294967295| 65535| 39| 2| 0|4537434 | 0| 814881181
x$kcccp结果显示控制文件low cache rba为全f,没有脏块。
建表,插入数据:
insert into hx.test select * from dba_objects;
1. 执行第一次日志切换:
SYS AS SYSDBA >alter system switch logfile;
重做日志文件使用状况:
SYS AS SYSDBA > select GROUP#,THREAD#,SEQUENCE#,STATUS ,FIRST_CHANGE#,NEXT_CHANGE# from v$log;
GROUP#| THREAD#| SEQUENCE#|STATUS |FIRST_CHANGE#|NEXT_CHANGE#
----------|----------|----------|----------------|-------------|------------
1| 1| 40|CURRENT | 4537578| 2.8147E+14
2| 1| 38|INACTIVE | 4537426| 4537434
3| 1| 39|ACTIVE | 4537434| 4537578
结果表明,3号日志有当前恢复需要的重做日志,而1号日志是当前使用的重做日志
查看报警日志,结果显示,分配了一个检查点任务,要求把RBA [0x28.2.10]之前的重做日志保护的脏数据写如数据文件,实际就是要求写完3号日志保护的buffer cache:
Beginning log switch checkpoint up to RBA [0x28.2.10], SCN: 4537578
Thread 1 advanced to log sequence 40 (LGWR switch)
Current log# 1 seq# 40 mem# 0: /u01/oradata2/hx/redo01.log
看x$kcccp表,当前low cache rba 仍是在3号重做日志:
SYS AS SYSDBA > SELECT CPLRBA_SEQ,CPLRBA_BNO,CPLRBA_BOF,CPODR_SEQ,CPODR_BNO,CPODR_BOF,CPODS,CPDRT,CPHBT FROM x$kcccp;
CPLRBA_SEQ|CPLRBA_BNO|CPLRBA_BOF| CPODR_SEQ| CPODR_BNO| CPODR_BOF|CPODS | CPDRT| CPHBT
----------|----------|----------|----------|----------|----------|----------------|----------|----------
39| 3| 0| 40| 2| 0|4537578 | 16| 814881304
2. 第二次日志切换:
SYS AS SYSDBA >alter system switch logfile;
SYS AS SYSDBA > select GROUP#,THREAD#,SEQUENCE#,STATUS ,FIRST_CHANGE#,NEXT_CHANGE# from v$log;
GROUP#| THREAD#| SEQUENCE#|STATUS |FIRST_CHANGE#|NEXT_CHANGE#
----------|----------|----------|----------------|-------------|------------
1| 1| 40|ACTIVE | 4537578| 4537591
2| 1| 41|CURRENT | 4537591| 2.8147E+14
3| 1| 39|ACTIVE | 4537434| 4537578
看重做日志文件,结果显示,3号日志和1好日志虽然不是当前使用的,但是包含了恢复需要的重做日志:
看告警日志文件,又分配了一个任务,要求把RBA [0x29.2.10]之前的重做日志保护的buffer cache写完:
Beginning log switch checkpoint up to RBA [0x29.2.10], SCN: 4537591
Thread 1 advanced to log sequence 41 (LGWR switch)
Current log# 2 seq# 41 mem# 0: /u01/oradata2/hx/redo02.log
看x$kcccp视图
SYS AS SYSDBA > SELECT CPLRBA_SEQ,CPLRBA_BNO,CPLRBA_BOF,CPODR_SEQ,CPODR_BNO,CPODR_BOF,CPODS,CPDRT,CPHBT FROM x$kcccp;
CPLRBA_SEQ|CPLRBA_BNO|CPLRBA_BOF| CPODR_SEQ| CPODR_BNO| CPODR_BOF|CPODS | CPDRT| CPHBT
----------|----------|----------|----------|----------|----------|----------------|----------|----------
39| 3| 0| 41| 2| 0|4537591 | 16| 814881318
3. 第三次切换
SYS AS SYSDBA >alter system switch logfile;
看日志文件,结果显示,1号和2号日志均没有恢复需要的重做日志了,可以猜想,他们保护的buffer cache全部写完了,他们对恢复不起作用了。
SYS AS SYSDBA >start log;
GROUP#| THREAD#| SEQUENCE#|STATUS |FIRST_CHANGE#|NEXT_CHANGE#
----------|----------|----------|----------------|-------------|------------
1| 1| 40|INACTIVE | 4537578| 4537591
2| 1| 41|INACTIVE | 4537591| 4537609
3| 1| 42|CURRENT | 4537609| 2.8147E+14
SYS AS SYSDBA >SELECT CPLRBA_SEQ,CPLRBA_BNO,CPLRBA_BOF,CPODR_SEQ,CPODR_BNO,CPODR_BOF,CPODS,CPDRT,CPHBT FROM x$kcccp;
看x$kcccp,很清楚,的确没有脏块了。
CPLRBA_SEQ|CPLRBA_BNO|CPLRBA_BOF| CPODR_SEQ| CPODR_BNO| CPODR_BOF|CPODS | CPDRT| CPHBT
----------|----------|----------|----------|----------|----------|----------------|----------|----------
4294967295|4294967295| 65535| 42| 3| 0|4537612 | 0| 814881333
那么,这其中都发生了什么呢?
告警日志文件:
Wed May 08 23:27:51 2013
Thread 1 cannot allocate new log, sequence 42
Checkpoint not complete
Current log# 2 seq# 41 mem# 0: /u01/oradata2/hx/redo02.log
Wed May 08 23:27:55 2013
Completed checkpoint up to RBA [0x29.2.10], SCN: 4537591
Completed checkpoint up to RBA [0x28.2.10], SCN: 4537578
Beginning log switch checkpoint up to RBA [0x2a.2.10], SCN: 4537609
Thread 1 advanced to log sequence 42 (LGWR switch)
Current log# 3 seq# 42 mem# 0: /u01/oradata2/hx/redo03.log
Completed checkpoint up to RBA [0x2a.2.10], SCN: 4537609
分析上面的日志,简单理解:企图切换到3号日志,但是oracle提示,3号日志包含了未完成的检查点,当前使用的还是2号文件。于是,oracle先把之前的脏块写完(包含1、2、3好重做日志保护的重做日志),再切换到3号日志,也就是说,这里实际是做了一次完全检查点。