揭密Oracle之七种武器之四:揭密Buffer Cache中的链表 揭密Oracle之 七种武器第一章 搭建测试环境(目前已到第三章) http://www.itpub.net/thread-1605241-1-1.html 揭密Oracle之七种武器二:DTrace语法:跟踪物理IO http://www.itpub.net/thread-1609235-1-
揭密Oracle之七种武器之四:揭密Buffer Cache中的链表
揭密Oracle之 七种武器 第一章 搭建测试环境(目前已到第三章)
http://www.itpub.net/thread-1605241-1-1.html
揭密Oracle之七种武器二:DTrace语法:跟踪物理IO
http://www.itpub.net/thread-1609235-1-1.html
揭密Oracle之七种武器之三:破译古老的谜题---共享CBC Latch的秘密
http://www.itpub.net/thread-1617245-1-1.html
还有一篇补遗:
揭密buffer Cache中的链表补遗
http://www.itpub.net/thread-1632432-1-1.html
有日了没写东西了。又是看房又是讲课,好多朋友问我是不是不写了,怎么会呢,分享知识,也是自我总结的一个过程,对自己的提高也是有帮助
的吗。
前段时间,一直有人问我Buffer Cache的链表,LRU、辅助LRU、检查点队列等等。检查点队列已经有很多文章讨论过了,我就不再重复的制造轮子
。
另外,还有主LRU冷热端的相关内容,这一块我也不再详细描述,因为也有相关的文章。
我主要说一下主LRU、辅助LRU和LRUW相关的内容。
本篇文章没有使用DTrace和GDB,难度较低,但实验内容较多。我先将结果写在前面,如果没时间按个看实验,大概了解一下也行。
1、辅助LRU的定位,是让进程可以在辅助LRU中尽快找到可用块,因此,辅助LRU中存放的,都是相较主LRU,更加没有用的块。比如全表扫描的
块,都会被放在辅助LRU。另外,当空间紧张时,前台进程会将脏块移到LRUW,这些从LRUW写出的脏块,写完成后也会放入辅助LRU。这样辅助LRU中块更
多,前台进程更加容易在辅助LRU中找到可用块。
2、非全表扫描,先到辅助LRU中寻找可有块,找到后会将其从辅助LRU中,移到主LRU冷端头。
3、如果非全表扫描较多,辅助LRU中块会越来越少,为了保持比例(辅助LRU占整个LRU总块数的20%到25%左右),SMON进程会3秒一次持有LRU
Latch,将主LRU冷端末的块移到辅助LRU。
4、全表扫描也先到辅助LRU中寻找可用块,但全表扫描的块仍将留在辅助LRU,不会调往主LRU冷端头。因此全表扫描的块将很快被覆盖。全表
扫描操作将只使用辅助LRU(也会用到主LRU,只会用到很少量的主LRU),一次大的全扫操作,可以将辅助LRU的所有块覆盖一遍或多遍。
5、数据库刚启动时,或刚Flush Buffer_cache时,所有块会被放在辅助LRU中。
6、前台进程(服务器进程)扫描主、辅LRU时,会将遇到的TCH为2以下的脏块,移到主LRUW。
7、DBWR三秒一次,扫描检查点队列的长度、确定是否要写脏块。另外,DBWR每三秒还会扫描主LRUW,将其中的脏块移到辅助LRUW,然后写到磁
盘。
8、从检查点队列写到磁盘中的块,不会改变它在LRU链表中的位置。从LRUW写到磁盘中的块,会被放于辅助LRU,以供马上覆盖。
基本上就这些了。下面开始吧。本篇比较简单,没有DTrace,更进一步的DTrace测试,放在下篇中吧。只是实验较多,繁琐了些。
第 一 章 一个测试理解什么是主、辅LRU
LRU是Buffer Cache池中的重要链表,它的作用我不再详述,已经有很多相关资料。这次主要和大家讨论下主LRU、辅助LRU的作用。
先来看一个测试。
步1:环境介绍
先来看看Buffer Cache的大小:
SQL> show sga
Total System Global Area 1073741824 bytes
Fixed Size 1284344 bytes
Variable Size 960497416 bytes
Database Buffers 104857600 bytes
Redo Buffers 7102464 bytes
Buffer Cache大小100M。
再来看看测试表大小:
SQL> set linesize 1000
SQL> col segment_name for a30
SQL> SQL> select segment_name,bytes/1024/1024 from dba_segments where segment_name='A3_70M';
SEGMENT_NAME BYTES/1024/1024
------------------------------ ---------------
A3_70M 80
SQL> SQL> select segment_name,bytes/1024/1024 from dba_segments where segment_name='A4_70M';
SEGMENT_NAME BYTES/1024/1024
------------------------------ ---------------
A4_70M 80
两张测试表,A3_70M、A4_70M,各自大小80M。本来是想把它们两个大小建为70M的,所以名字带有后缀_70M,但建的大了一点,不过,这无所
谓,不会影响我们的测试结果。
步2:刷新Buffer Cache池,观察LRU链长度
SQL> alter system flush buffer_cache;
SQL> select CNUM_SET,CNUM_REPL,ANUM_REPL,CNUM_WRITE ,ANUM_WRITE from x$kcbwds;
CNUM_SET CNUM_REPL ANUM_REPL CNUM_WRITE ANUM_WRITE
---------- ---------- ---------- ---------- ----------
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
6238 6238 6219 0 0
6237 6237 6224 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
CNUM_SET CNUM_REPL ANUM_REPL CNUM_WRITE ANUM_WRITE
---------- ---------- ---------- ---------- ----------
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
16 rows selected.
这个视图返回16行,但只有两行有数据。其他行都是0,原因是什么?
查一下select name from v$latch_children where name ='cache buffers lru chain'; 就可以知道,我一共有16个cache buffers lru
chain Latch,但只有两个被使用了。
每一个cache buffers lru chain latch,都算作一个“工作组”,work set。意义是指每个cache buffers lru chain latch,都对应一组主
、辅LRU链,和脏LRU链。
x$kcbwds视图中CNUM_SET列,统计工作组所有块数量。简单点说,也就是LRU链上所有块的数量。
CNUM_REPL列是主、辅LRU链表中所有的块数。
ANUM_REPL列是辅LRU链上所有的块数。
用CNUM_REPL-ANUM_REPL,即为所有主LRU链上的块数。
CNUM_WRITE、ANUM_WRITE这个列对应LRUW(也即为脏LRU)链,CNUM_WRITE为LRUW链中所有块数,ANUM_WRITE为辅助LRUW中的块数。两个相减,
要以得到主LRUW中的块数。
这个测试中,先不用观察LRUW。我们先只关注主、辅LRU。
来看我们的显示结果吧,两个工作组加起来,一共12475个块。
另外,可以看到,CNUM_REPL的ANUM_REPL列的几乎相等,这说明绝大多数块,都在辅助LRU链表中,这是为什么呢?这是我们今天第一点结论,
flush了buffer cache、或刚刚开启数据库时,所有的可用Buffer都会链接到辅助LRU中。
步3:先查询a3_70M,再查询a4_70M,分别观察物理读。(注意顺序,先查询A3_70M,再查询A4_70M)
SQL> set autot trace
SQL> select count(*) from a3_70m;
Execution Plan
----------------------------------------------------------
Plan hash value: 850873958
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
---------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1643 (1)| 00:00:20 |
| 1 | SORT AGGREGATE | | 1 | | |
| 2 | TABLE ACCESS FULL| A3_70M | 1511K| 1643 (1)| 00:00:20 |
---------------------------------------------------------------------
Statistics
----------------------------------------------------------
207 recursive calls
0 db block gets
9292 consistent gets
9271 physical reads
0 redo size
414 bytes sent via SQL*Net to client
381 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
4 sorts (memory)
0 sorts (disk)
1 rows processed
SQL> select count(*) from a4_70m;
Execution Plan
----------------------------------------------------------
Plan hash value: 2047399966
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
---------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1643 (1)| 00:00:20 |
| 1 | SORT AGGREGATE | | 1 | | |
| 2 | TABLE ACCESS FULL| A4_70M | 1502K| 1643 (1)| 00:00:20 |
---------------------------------------------------------------------
Statistics
----------------------------------------------------------
207 recursive calls
0 db block gets
9292 consistent gets
9259 physical reads
0 redo size
414 bytes sent via SQL*Net to client
381 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
4 sorts (memory)
0 sorts (disk)
1 rows processed
可以看到,第一次查询A3_70M和A4_70M,物理读分别是9200多。
步4:再次查询a3_70M,再查询a4_70M,分别观察物理读。(顺序无所谓,先查谁都可以)
SQL> select count(*) from a3_70m;
Execution Plan
----------------------------------------------------------
Plan hash value: 850873958
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
---------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1643 (1)| 00:00:20 |
| 1 | SORT AGGREGATE | | 1 | | |
| 2 | TABLE ACCESS FULL| A3_70M | 1511K| 1643 (1)| 00:00:20 |
---------------------------------------------------------------------
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
9269 consistent gets
213 physical reads
0 redo size
414 bytes sent via SQL*Net to client
381 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL> select count(*) from a4_70m;
Execution Plan
----------------------------------------------------------
Plan hash value: 2047399966
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
---------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1643 (1)| 00:00:20 |
| 1 | SORT AGGREGATE | | 1 | | |
| 2 | TABLE ACCESS FULL| A4_70M | 1502K| 1643 (1)| 00:00:20 |
---------------------------------------------------------------------
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
9269 consistent gets
9135 physical reads
0 redo size
414 bytes sent via SQL*Net to client
381 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
发现没有,A3_70M的物理读在第二次查询时大量下降,只有213次。而A4_70M在第二次查询时,物理读下降很不明显,还是9200次左右。
可以再测个几遍,情况依旧。A3_70M的物理读很少,而A4_70M的物理读很多。
这是我们今天的问题一:考虑这是为什么。
下面继续我们的问题2。
步5:连续两次查询A3_70M和A4_70M
SQL> select count(*) from a4_70m;
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
9269 consistent gets
8686 physical reads
0 redo size
414 bytes sent via SQL*Net to client
381 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL> select count(*) from a4_70m;
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
9269 consistent gets
8722 physical reads
0 redo size
414 bytes sent via SQL*Net to client
381 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
这是两次A4_70M的结果对比,多次查询A4_70M之后,物理读下降到了8700多,然后不再下降。
再来看两次连续查询A3_70M的结果对比:
SQL> select count(*) from a3_70m;
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
9269 consistent gets
600 physical reads
0 redo size
414 bytes sent via SQL*Net to client
381 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL> select count(*) from a3_70m;
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
9269 consistent gets
0 physical reads
0 redo size
414 bytes sent via SQL*Net to client
381 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
第一次有600次物理读,第二次就没有了。
问题2出来了,考虑这是没什么。
步6:暂时总结
1、A3_70M和A4_70M表大小一样。行数一样、所占空间也一样,都是80M
2、第一次查询时,我们是先查询A3_70M,再查A4_70M。如果你反过来,先查A4_70M,将看到A4_70M的物理读在第二次查询时大大减少,连续两
次查询时物理读为0。
两个问题:
1、为什么第一次查询时,先查的表,在第二次查询时物理读会大大减少,而后查的表则不会。
2、为什么第一次查询时,先查的表连续两次查询时物理读会减少为0,而后查的表不会。
一个提示:可以观察一下我们开始提到的x$kcbwds视图。
步7:如何观察块处在哪个链上。
如何进一步分析这个问题?其实这两个问题的回答,都需要着落到一件事上,就是要分析两个表对应的块,分别在什么链上。是在主LRU,还是
辅LRU。如何进行这个观察呢?其实很简单,x$bh中有个LRU_FLAG列,通过这个列,就可以确定块在哪个链表上。
我们来看一下这个视图:
SQL> select lru_flag,count(*) from x$bh group by lru_flag;
LRU_FLAG COUNT(*)
---------- ----------
6 3081
2 6734
8 2
0 2658
在我的测试库中,我观察到的LRU_FLAG有4种值,6、2、8和0。这4个值分别代别什么意义呢?确定这个其实很简单,DUMP一下块对应的Buffer
就行了,以LRU_FLAG为6的为例:
(1)、随便查找LRU_FLAG为6的一行:
SQL> select lru_flag,file#,dbablk,TS# from x$bh where LRU_FLAG=6 and rownum=1;
LRU_FLAG FILE# DBABLK TS#
---------- ---------- ---------- ----------
6 5 12459 4
(2)、如下DUMP一下这个BUffer:
SQL> alter session set events 'immediate trace name SET_TSN_P1 level 5';
Session altered.
SQL> alter session set events 'immediate trace name BUFFER level 0x014030ab';
Session altered.
上面这两个命令,“SET_TSN_P1 level 5”中的5,是表空间号加1得到的。也就是TS#+1。
第二个命令中“BUFFER level 0x014030ab”,其中0x014030ab,是5号文件12459号块的DBA。
这个DBA是如何计算出的?其实我是在另一个会话中DUMP一下5号文件12459号块,查看DUMP结果才得到的,Oracle也提供了一个包,其实有个函
数可以根据文件号、块号生成DBA,包名、函数名我都忘了,只好用笨方法。
好,来查看DUMP结果吧,到user_dump_dest目录中,找到日期最靠后的(我一般是ls -lFrt),它就是我们刚刚生成的DUMP文件。在它的开头,
我们可以看到如下内容:
*** 2012-07-03 08:58:36.571
*** SERVICE NAME
SYS$USERS) 2012-07-03 08:58:36.523
*** SESSION ID
874.3) 2012-07-03 08:58:36.523
Dump of buffer cache at level 10 for tsn=4, rdba=20983979
BH (7bbf0764) file#: 5 rdba: 0x014030ab (5/12459) class: 1 ba: 7b9dc000
set: 5 blksize: 8192 bsi: 0 set-flg: 0 pwbcnt: 0
dbwrid: 0 obj: 9738 objn: 9738 tsn: 4 afn: 5
hash: [7d7e687c,8e94b67c] lru: [7bbe80b8,7bbf0704]
lru-flags: moved_to_tail on_auxiliary_list
ckptq: [NULL] fileq: [NULL] objq: [7bbf075c,7bbe8110]
st: XCURRENT md: NULL tch: 0
flags: only_sequential_access
LRBA: [0x0.0.0] HSCN: [0xffff.ffffffff] HSUB: [65535]
buffer tsn: 4 rdba: 0x014030ab (5/12459)
scn: 0x0000.00418edb seq: 0x01 flg: 0x06 tail: 0x8edb0601
frmt: 0x02 chkval: 0xc400 type: 0x06=trans data
我对这里面的东西,不过多解释了,其实有一行:
lru-flags: moved_to_tail on_auxiliary_list
这一行说明了块对应Buffer所在LRU链表的状态。Buffer目前在辅助LUR,moved_to_tail说明Buffer正移向链表末尾。
好了,我们已经总结出来,LRU_FLAG列为6,说明Buffer在辅助LRU链表。
其他的我不再一一列出测试过程了,总结一下,LRU_FLAG为6、4,说明BUffer在辅助LRU中。为0、2,说明在主LRU的冷端,为8、9的,说明在
主LRU的热端。
步8:问题1:为什么第一次查询时,先查的表,在第二次查询时物理读会大大减少,而后查的表则不会
让我们来观察一下A3_70M、A4_70M 的块都在什么链上
SQL> select lru_flag,count(*) from x$bh a,dba_objects b where a.obj=b.data_object_id and object_name='A3_70M' group by lru_flag;
LRU_FLAG COUNT(*)
---------- ----------
2 8554
0 1
SQL> select lru_flag,count(*) from x$bh a,dba_objects b where a.obj=b.data_object_id and object_name='A4_70M' group by lru_flag;
LRU_FLAG COUNT(*)
---------- ----------
6 3082
2 366
0 1
这两条语句,我就不再过多解释了,根据LRU_FLAG列的值,我们可以确认,A3_70M,共有8555个块在Buffer Cache中,所有相关的块都在主LRU
链。A4_70M,只有3400多个块在Buffer Cache中,而且大部分块都在辅助LRU。
为什么A3_70M的块,被读进Buffer Cache时,大多被放置在主LRU的冷端?而A4_70M的块,则被放进辅助LRU呢?
能回答这个问题,我们今天的问题也就有了答案。
步9:回答问题1
有如下三点基础知识:
1、当数据库刚刚打开、或刚刚做过FLush Buffer_cache操作时,所有的块,都被放进辅助LRU。
2、进程寻找可用块时,先会在辅助LRU中查找,然后才会到主LRU中找。
3、Oracle会保持20%至25%左右的块在辅助LRU链,80%的在主LRU。这一点,从观察x$kcbwds中的CNUM_REPL、ANUM_REPL列,可以很容易的验证
。
有了这三点基础知识,让我们来得出结论吧:
当所有块都在辅助LRU时,Oracle为了急于保证主、辅LRU,块数量80%,20%的比例,第一次查询时,会将大量的块移到主LRU。也就是说,第一
次查询A3_70M时,大至步骤是这样的,分两种情况:
一、主LRU几乎为空时:
1、在辅助LRU找到可用块。
2、将它移到主LUR
3、将数据读入此块。
二、当主、辅LRU的块数量比例到达80%、20%后
1、在辅助LRU找到查用块
2、所处链表不变,直接将数据读入此块。
3、物理读完成后,块还在辅助LRU中。
由于第一次查询A3_70M时,主LRU几乎为空,所以,它的绝大部分的块被放在了主LRU,少部分块被放入了辅助LRU。
根据比例,辅助LRU中将会有3000到3200个左右的块。根据我们的测试结果,全表扫描A4_70M后,有3000多个块在辅助链上。根据这个,得出我
们今天第二个结论,正常的全表扫描操作,将只会反复使用辅助LRU中的块。
当然,我们的测试中,还是有少量A4_70M的块进入到了主LRU,不过数量不多,只有几百个。
好了,回答一开始的问题吧,第一次查询A3_70M,Oracle会了保持主、辅LRU的比例,将很多被A3_70M占用的块移进了主LRU,所以,A3_70M在
Buffer Cache中可以有很多块,再次查询A3_70M时,物理读大大减少。
而全表扫描A4_70M,只能反复使用辅LRU中的块,几乎无法占用主LRU中的块,因此在Buffer Cache中块数较少,每次物理读都很高。
步10:回答问题2
为什么连续两次查询A3_70M,就没有物理读了?可以连续两次全表扫描一下A3_70M,查看结果:
SQL> select lru_flag,count(*) from x$bh a,dba_objects b where a.obj=b.data_object_id and object_name='A3_70M' group by lru_flag;
LRU_FLAG COUNT(*)
---------- ----------
6 1004
2 8254
0 1
一共9000多个块,8000多个在主LRU,1000多一点在辅助LRU。A3_70M表的全部块,都可以被缓存到Buffer Cache中。
当再次查询A4_70M时,A4_70M将又反复占用辅助LRU中的块,A3_70M留在辅助LRU中的1000多一点块会被覆盖,所以,当A3_70M、A4_70M交替查
询时,每次A3_70M在辅助LRU中的块都会被覆盖,因此,它会有少量物理读,但,当连续查询A3_70M时,辅助LRU中的块没被覆盖,就不会有物理读了。
好,问题已经解答了,让我们总结一下吧。
总结:
1、flush了buffer cache、或刚刚开启数据库时,所有的可用块都会链接到辅助LRU中。
2、查找可用块的过程,是先找辅助LRU,再从主LRU的冷端尾开始找。
3、全表扫描在辅助LRU找到块后,不会将块读进主LRU。所以,全表扫描的结果很容易被覆盖。
4、非全表扫描时,在辅助LRU找到块后,会将块移到主LRU的冷端头。这一点我们还没验证过。
5、全表扫描的块,如果再次以非全表扫描方式访问,TCH列会增加,但会一直留在辅助LRU,不会被移到主LRU冷端头。只有等到TCH值超过2时,才会在
下次被扫描到时,移到主LRU热端。
第4点和第5点,我们并没用测试验证,但找个这样的测试也是很简单的。留给大家自己验证一下吧,研究Internal,结果不是目的,过程更有
意义。
最后还有一个注意事项,这个测试只能在10G下做,因为11GR2后有些改变,即使是开库后第一次全表扫描,也不会把块移往主LRU,这样其实更合理,Oracle其实一直没有停止对内核优化步伐。
另外,本篇中除这个测试外,其他测试都可以在10G、11GR2下做。
第 二 章 LRUW
脏块什么时候会进入LRUW ? 流行的说法有以下几种:
1、DBWR 每3秒醒来时,会将脏块收集到LRUW,当LRUW中脏块达到一定阀值时,将触发DBWR写LRUW中的脏块。
2、当前台进程(也就是服务器进程)扫描LRU时,会将发现的脏块移到LRUW中,当LRUW中脏块达到一定阀值时,将触发DBWR写LRUW中的脏块。
3、当前台进程扫描LRU的一定数量的块后,都没有发现可用块,此时会触发紧急写。前台进程等待,DBWR将脏块从检查点队列移到LRUW,从
LRUW写到磁盘。
以上这三种说法,第一种并不存在,或者说,都观察不到。
通过x$kcbwds视图的CNUM_WRITE、ANUM_WRITE列,就可以观察到主、辅LRUW链的长度。可以一直不停的多观察几十秒,没有发现这两个列不为0
。3秒的说法,不功自破。
但是,并不能说3秒写磁盘的操作不存在,只是,并不是以这种形式。
第二种说法,流传很广,也的确会有这种情况出现,但真实情况和流行说法还是会有不同。
具体怎么来验证一下呢。先来做个测试:
步1:环境配置:
写脏块有两种途径:通过检查点队列、通过LRUW。为了避免从检查点队列写影响我们的测试结果,我们要将增量检查点关闭。
其实,不是关闭,而是将增量检查点周期设的很大,在我们的测试期间,保证它不会触发。
需要设置如下几个参数:
SQL> show parameter log_checkpoint_timeout
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
log_checkpoint_timeout integer 100000
SQL> show parameter mttr
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
fast_start_mttr_target integer 0
将log_checkpoint_timeout设置为了10万秒。fast_start_mttr_target设置为0。
另外,10G之后,fast_start_mttr_target设置为0时,Oracle将打开一个Self Tune Checkpoint(自调节检查点)的功能,将它也关闭:
SQL> alter system set "_disable_selftune_checkpointing"=true;
System altered.
经过这样的设置,增量检查点虽然没有关闭,也和关闭差不多了,10万秒才会被触发。
当然,日志切换时还会触发一个“日志切换时的增量检查点”,虽和普通的增量检查点有不同,但大体上类似增量检查点。这个只要建个稍大
点的日志文件,就可以解决,比如,我的日志文件是500M。
步2:观察当前系统中的脏块
SQL> select count(*) from v$bh where dirty='Y';
COUNT(*)
----------
46
步2:在另一个会话中,扫描个非全表扫描
select /*+index(a2_70m)*/ * from a2_70m where id1<=100000000;
索引范围扫描,将先找辅助LRU、再找主LRU。
步3:在步2中的索引范围扫描执行期间,不停的显示:
select CNUM_SET,CNUM_REPL,ANUM_REPL,CNUM_WRITE ,ANUM_WRITE from x$kcbwds where cnum_set>0;
select * from v$sysstat where name in ('dirty buffers inspected');
第一条语句可以查看LRUW的长度信息,第二条语句,可以观察前台进程在扫描LRU时,跳过的脏块数。我们可以观察到如下结果:
…………………………………………省略一部分相同的结果……………………………………………………
SQL> select * from v$sysstat where name in ('dirty buffers inspected');
STATISTIC# NAME CLASS VALUE STAT_ID
---------- ---------------------------------------------------------------- ---------- ---------- ----------
94 dirty buffers inspected 8 650 1344569897
SQL>
SQL> select CNUM_SET,CNUM_REPL,ANUM_REPL,CNUM_WRITE ,ANUM_WRITE from x$kcbwds where cnum_set>0;
CNUM_SET CNUM_REPL ANUM_REPL CNUM_WRITE ANUM_WRITE
---------- ---------- ---------- ---------- ----------
6238 6238 1522 0 0
6237 6237 1541 0 0
SQL> select * from v$sysstat where name in ('dirty buffers inspected');
STATISTIC# NAME CLASS VALUE STAT_ID
---------- ---------------------------------------------------------------- ---------- ---------- ----------
94 dirty buffers inspected 8 650 1344569897
SQL>
SQL> select CNUM_SET,CNUM_REPL,ANUM_REPL,CNUM_WRITE ,ANUM_WRITE from x$kcbwds where cnum_set>0;
CNUM_SET CNUM_REPL ANUM_REPL CNUM_WRITE ANUM_WRITE
---------- ---------- ---------- ---------- ----------
6238 6237 1523 1 0
6237 6235 1541 2 0
SQL> select * from v$sysstat where name in ('dirty buffers inspected');
STATISTIC# NAME CLASS VALUE STAT_ID
---------- ---------------------------------------------------------------- ---------- ---------- ----------
94 dirty buffers inspected 8 653 1344569897
SQL>
SQL> select CNUM_SET,CNUM_REPL,ANUM_REPL,CNUM_WRITE ,ANUM_WRITE from x$kcbwds where cnum_set>0;
CNUM_SET CNUM_REPL ANUM_REPL CNUM_WRITE ANUM_WRITE
---------- ---------- ---------- ---------- ----------
6238 6237 1520 1 0
6237 6235 1539 2 0
SQL> select * from v$sysstat where name in ('dirty buffers inspected');
STATISTIC# NAME CLASS VALUE STAT_ID
---------- ---------------------------------------------------------------- ---------- ---------- ----------
94 dirty buffers inspected 8 653 1344569897
…………………………………………省略一部分相同的结果……………………………………………………
SQL>
SQL> select CNUM_SET,CNUM_REPL,ANUM_REPL,CNUM_WRITE ,ANUM_WRITE from x$kcbwds where cnum_set>0;
CNUM_SET CNUM_REPL ANUM_REPL CNUM_WRITE ANUM_WRITE
---------- ---------- ---------- ---------- ----------
6238 6238 1507 0 0
6237 6237 1527 0 0
SQL> select * from v$sysstat where name in ('dirty buffers inspected');
STATISTIC# NAME CLASS VALUE STAT_ID
---------- ---------------------------------------------------------------- ---------- ---------- ----------
94 dirty buffers inspected 8 653 1344569897
…………………………………………省略一部分相同的结果……………………………………………………
前面的观察结果,dirty buffers inspected的值一开始为650,当变为653时,同时,可以在CNUM_WRITE列看到,此列的值增加了3。
这证明有三个脏块被跳过,移到了LRUW上。但是,不同于通常的说法,没有等LRUW中脏块数达到什么阀值,很快的,这三个脏块就被写的磁盘
了。
3个块脏块,绝对没有到达任何阀值。那么,是什么情况触发的写脏块呢?
3秒,是否是三秒呢?
其实测试很简单,set time on可以打开时间提示符,打开提示符后再试:
13:23:25 SQL> select CNUM_SET,CNUM_REPL,ANUM_REPL,CNUM_WRITE ,ANUM_WRITE from x$kcbwds where cnum_set>0;
CNUM_SET CNUM_REPL ANUM_REPL CNUM_WRITE ANUM_WRITE
---------- ---------- ---------- ---------- ----------
6238 6238 1523 0 0
6237 6237 1549 0 0
13:23:25 SQL> select CNUM_SET,CNUM_REPL,ANUM_REPL,CNUM_WRITE ,ANUM_WRITE from x$kcbwds where cnum_set>0;
CNUM_SET CNUM_REPL ANUM_REPL CNUM_WRITE ANUM_WRITE
---------- ---------- ---------- ---------- ----------
6238 6231 1542 7 0
6237 6237 1512 0 0
13:23:25 SQL> select CNUM_SET,CNUM_REPL,ANUM_REPL,CNUM_WRITE ,ANUM_WRITE from x$kcbwds where cnum_set>0;
CNUM_SET CNUM_REPL ANUM_REPL CNUM_WRITE ANUM_WRITE
---------- ---------- ---------- ---------- ----------
6238 6206 1510 32 0
6237 6237 1505 0 0
……………………………………省略部分相同内容……………………………………
13:23:26 SQL> select CNUM_SET,CNUM_REPL,ANUM_REPL,CNUM_WRITE ,ANUM_WRITE from x$kcbwds where cnum_set>0;
CNUM_SET CNUM_REPL ANUM_REPL CNUM_WRITE ANUM_WRITE
---------- ---------- ---------- ---------- ----------
6238 6195 1509 43 0 <--------------此处主LRUW上有43个脏块
6237 6237 1515 0 0
……………………………………省略部分相同内容……………………………………
13:23:27 SQL> 13:23:27 SQL> select CNUM_SET,CNUM_REPL,ANUM_REPL,CNUM_WRITE ,ANUM_WRITE from x$kcbwds where cnum_set>0;
CNUM_SET CNUM_REPL ANUM_REPL CNUM_WRITE ANUM_WRITE
---------- ---------- ---------- ---------- ----------
6238 6195 1504 43 43 <--------------主LRUW上的43个,被移到了辅助LRUW上。
6237 6227 939 10 10
……………………………………省略部分相同内容……………………………………
13:23:27 SQL> select CNUM_SET,CNUM_REPL,ANUM_REPL,CNUM_WRITE ,ANUM_WRITE from x$kcbwds where cnum_set>0;
CNUM_SET CNUM_REPL ANUM_REPL CNUM_WRITE ANUM_WRITE
---------- ---------- ---------- ---------- ----------
6238 6238 1538 0 0
6237 6237 940 0 0
在13:23:25时,有脏块被移到LRUW,我没有将dirty buffers inspected的值显示出来,其实dirty buffers inspected正好也增加了7。之后
脏块数慢慢增加,到13:23:27,两条LRUW上的脏块已经合计53个。
而且,可能很容易观察到,13:23:27 时,有43个脏块,从主LRUW移到辅助LRUW,然后被写到磁盘。
从13:23:25 ,到13:23:27,正好3秒。
你可以做更多的测试,我的测试结果,每次LRUW上有脏块,总是不超过3秒,就会被写到磁盘。
如果你觉得这个测试太简单了,猜测的部分太多,没关系,耐心等下,后面会有DTrace版的验证方法。这里不再详述。
回到开头的流利说法:
1、DBWR 每3秒醒来时,会将脏块收集到LRUW,当LRUW中脏块达到一定阀值时,将触发DBWR写LRUW中的脏块。
2、当前台进程(也就是服务器进程)扫描LRU时,会将发现的脏块移到LRUW中,当LRUW中脏块达到一定阀值时,将触发DBWR写LRUW中的脏块。
其实这两种说法都有不准确的地方,准确说是这样的:
1、DBWR 每3秒醒来,之后它会做两件事:
(1)、检查检查点队列长度,如果脏块太多、恢复时间有可能超过fast_start_mttr_target参数的值,开始沿着检查点队列写脏块。
(2)、检查LRUW,有脏块就写。
2、当前台进程(也就是服务器进程)扫描LRU时,会将发现的脏块移到LRUW中,等待3秒一次DBWR醒来,将脏块写磁盘。
前面说的流行说法还有第三点:
3、当前台进程扫描LRU的一定数量的块后,都没有发现可用块,此时会触发紧急写。前台进程等待,DBWR将脏块从检查点队列移到LRUW,从
LRUW写到磁盘。
这种说法容易理解,这是紧急情况。前台进程已经在等待Free Buffer Waits了,DBWR不会再按检查点队列依次写,而是从LRU中碰到脏块就移
到LRUW、然后写到磁盘。从LRUW写和从检查点队列写区别是什么呢?为什么这种紧急情况写,就要从LRUW写呢?
下面继续。
第 三 章 写完的脏块如何处理
从LRUW写和从检查点队列写区别是什么呢?为什么这种紧急情况写,就要从LRUW写呢?
回答这个问题,主要要看写完的脏块会如何处理。
如果脏块从检查点队列中写到磁盘,脏块在LRU、LRUW链表的位置,不会任何变化,这一点很容易证明:
SQL> select dbms_rowid.ROWID_RELATIVE_FNO(rowid),dbms_rowid.rowid_block_number(rowid),id1,id2 from a2_70m where id1<=1;
DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) ID1 ID2
------------------------------------ ------------------------------------ ---------- ----------
4 20 1 10
我从A2_70M选择一行,它在4号文件20号块。
然后用如下语句观察x$bh中它的状态:
set pagesize 50000
set linesize 10000
select file#,dbablk,tch,lru_flag,ba,decode(state,0,'free',1,'xcur',2,'scur',3,'cr', 4,'read',5,'mrec',6,'irec',7,'write',8,'pi',
9,'memory',10,'mwrite',11,'donated'),
decode(bitand(flag,1), 0, 'N', 'Y') dirty,US_NXT,US_PRV,LRU_FLAG from x$bh a where file#=4 and dbablk=20
order by FILE# , DBABLK;
结果如下:
FILE# DBABLK TCH LRU_FLAG BA DECODE( D US_NXT US_PRV LRU_FLAG
---------- ---------- ---------- ---------- -------- ------- - -------- -------- --------
4 20 2 0 80AEA000 xcur N 80BF6694 80BF6694 0
从Dirty列可以看到,它并不是一个脏块。下面修改它:
SQL> update a2_70m set id2=id2+0 where id1=1;
1 row updated.
再次查看:
22:28:54 SQL> /
FILE# DBABLK TCH LRU_FLAG BA DECODE( D US_NXT US_PRV LRU_FLAG
---------- ---------- ---------- ---------- -------- ------- - -------- -------- --------
4 20 2 0 80AEA000 xcur Y 80BF6694 80BF6694 0
已经是个脏块了。LRU_FLAG为0,说明它在主LRU上。这是因为ID1列上有索引,更新产生的读操作,是非全表扫描,所以它被放在主LRU上。全
表扫描的块,将被保留在辅助LRU中。
将增量检查点改的频繁些:
SQL> alter system set log_checkpoint_timeout=5;
System altered.
5秒一次,很快的,4号文件20号块已经不是脏块了,这是观察结果:
22:31:18 SQL> /
FILE# DBABLK TCH LRU_FLAG BA DECODE( D US_NXT US_PRV LRU_FLAG
---------- ---------- ---------- ---------- -------- ------- - -------- -------- --------
4 20 2 0 80AEA000 xcur N 80BF6694 80BF6694 0
虽然不是脏块了,但它的LRU_FLAG列值,US_NXT、US_PRV列值,都没有变化。
这说明脏块虽然被写到磁盘中了,但它在Buffer Cache众链表中的位置,没有变化。
再来看看从LRUW中写出的情况,将log_checkpoint_timeout重新改回很大的值:
SQL> alter system set log_checkpoint_timeout=100000;
System altered.
再次修改4号文件20号块:
SQL> update a2_70m set id2=id2+0 where id1=1;
1 row updated.
确认它已经是脏块了:
22:43:39 SQL> /
FILE# DBABLK TCH LRU_FLAG BA DECODE( D US_NXT US_PRV LRU_FLAG
---------- ---------- ---------- ---------- -------- ------- - -------- -------- --------
4 20 1 0 80AEA000 xcur Y 80BF6694 80BF6694 0
下面将a4_70m改为CACHE。目的是让它可以进入主LRU:
SQL> alter table lhb.a4_70m cache;
Table altered.
全扫描一个a4_70m:
select count(*) from a4_70m;
再来观察4号文件20号块:
22:51:21 SQL> /
FILE# DBABLK TCH LRU_FLAG BA DECODE( D US_NXT US_PRV LRU_FLAG
---------- ---------- ---------- ---------- -------- ------- - -------- -------- --------
4 20 1 4 7B72E000 xcur N 7B7F7E7C 7B7F7E7C 4
已经不是脏块了,但它的LRU_FLAG列值为4,说明它已经被移到辅助LRU中了。并且,它的US_NXT、US_PRV列指针也都变化了。
这就是从检查点队列和从LRUW写脏块的最大区别,从检查点队列写完脏块,脏块只是变的不脏了,其他没有任何变化。但从LRUW写完脏块,块
要被放入辅助LRU,这样,块将会被很快覆盖掉。
Oracle之所设计两种写脏块模式,就是为了应对脏块较多的紧急情况。此时如果还从检查点队列写,辅助LRU中的块数量不会增加,前台进程还
是无法快速找到可用块。如果从LRUW写,写完的块被放入辅助LRU,只要这些脏块的TCH值不超过2,它们马上就可以被其他进程重用。
好了,关于LRUW,还有一个问题,我刚才的实验,update a2_70m set id2=id2+0 where id1=1; ,这条语句,会将块放在主LRU中,如果块在
辅助LRU中,又会怎样呢?下面来测试下:
先全扫描a2_70m:
SQL> select * from a2_70m;
此时查看4号文件20号块:
22:56:11 SQL> /
FILE# DBABLK TCH LRU_FLAG BA DECODE( D US_NXT US_PRV WA_NXT WA_PRV LRU_FLAG
---------- ---------- ---------- ---------- -------- ------- - -------- -------- -------- -------- ----------
4 20 1 4 7C6C2000 xcur N 7C7F5884 7C7F5884 7C7F588C 7C7F588C 4
LRU_FLAG为4,代表它在辅助LRU中,因为还没有修改,所以它不是一个脏块。
执行Update:
SQL> update a2_70m set id2=id2+0 where id1=1;
1 row updated.
再查看4号文件20号块的状态:
22:56:12 SQL> /
FILE# DBABLK TCH LRU_FLAG BA DECODE( D US_NXT US_PRV WA_NXT WA_PRV LRU_FLAG
---------- ---------- ---------- ---------- -------- ------- - -------- -------- -------- -------- ----------
4 20 2 4 7C6C2000 xcur Y 7C7F5884 7C7F5884 7C7F588C 7C7F588C 4
已经是脏块了,但LRU_FLAG仍为4,仍在辅助LRU中。
好,已经达到我们的目的,在辅助LRU中制造一个脏块。
下面全扫描a3_70m,在辅助LRU中占用大量块。4号文件20号块会被覆盖吗:
22:56:28 SQL> /
FILE# DBABLK TCH LRU_FLAG BA DECODE( D US_NXT US_PRV WA_NXT WA_PRV LRU_FLAG
---------- ---------- ---------- ---------- -------- ------- - -------- -------- -------- -------- ----------
4 20 0 8 7C6C2000 xcur Y 7C7F5884 7C7F5884 7C7F588C 7C7F588C 8
没有被覆盖,LRU_FLAG状态为8,说明已经移到主LRU热端了。这是因为它的TCH值大于、等于2。但它仍是脏块。
- UNDO段头块格式深度解析
- 图文并茂Mutex性能问题解析(一)
- os认证以及口令文件的一点总结!
- buffer busy waits,我的看法
- 明明白白使用数据块 ----数据块格式深入解析
- 12c active dataguard本机搭建和基础测试
- 无私奉献 企业级DBA--学习手册(中文版)体系结构,性能优化
- 揭密Oracle之七种武器之三:破译古老的谜题---共享CBC Latch的秘密
- 揭密Oracle之七种武器之四:揭密Buffer Cache中的链表
- VAGE的探索频道:Oracle时间之旅