Home >Database >Mysql Tutorial >揭密Oracle之七种武器之四:揭密Buffer Cache中的链表【主】

揭密Oracle之七种武器之四:揭密Buffer Cache中的链表【主】

WBOY
WBOYOriginal
2016-06-07 15:50:231291browse

揭密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揭密Oracle之七种武器之四:揭密Buffer Cache中的链表【主】SYS$USERS) 2012-07-03 08:58:36.523
*** SESSION ID揭密Oracle之七种武器之四:揭密Buffer Cache中的链表【主】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 索引范围扫描,将先找辅助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         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         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
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时间之旅
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn