oracle视图链表,揭密Oracle之七种武器之四:揭密Buffer Cache中的链表

本帖最后由 vage 于 2012-10-15 11:44 编辑

揭密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

c58e339c7046a1ffce9c5508745874fa.gifSYS$USERS) 2012-07-03 08:58:36.523

*** SESSION ID

c58e339c7046a1ffce9c5508745874fa.gif874.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  

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<=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。但它仍是脏块。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值