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 NAMESYS$USERS) 2012-07-03 08:58:36.523
*** SESSION ID874.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下做。

 

【本文转载自 www.cuug.com网站】