oracle内核学习总结

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

http://www.itpub.net/thread-1631537-1-1.html


揭密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 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下做。




    第 二 章       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。但它仍是脏块



========

《Oracle内核技术揭秘》

VAGE,现任eBay Principie DBA,曾任 
  阿里巴巴高级数据库专家,京东T5级技术专家, 
  
开创“调试Oracle”领域,精通DTrace、 
  gdb/mdb调试Oracle,是国内唯一有能力阅读 
  Oracle反汇编代码的DBA。凭一己之力,突破了 
  Oracle技术封锁,在网络中分享了多篇深入解析 
  Oracle的技术文章,被誉为Oracle传奇技术大师。 
  
图书目录
前言 
  第1章存储结构 
  1.1区:表空间中的基本单位 
  1.1.1统一区大小表空间和区的使用规则 
  1.1.2系统管理区大小 
  1.1.3碎片:少到可以忽略的问题 
  1.2段中块的使用 
  1.2.1块中空间的使用 
  1.2.2典型问题:堆表是有序的吗 
  1.2.3ASSM与L3、L2、L1块的意义 
  1.2.4值得注意的案例:ASSM真的能提高插入并发量吗 
  1.2.5段头与Extent Map 
  1.2.6索引范围扫描的操作流程 
  第2章调优排故方法论 
  2.1调优排故的一般步骤 
  2.1.1常见DUMP和Trace文件介绍 
  2.1.2等待事件 
  2.1.3各种资料视图介绍 
  2.1.4等待事件的注意事项 
  2.2AWR概览 
  2.2.1AWR报告的注意事项 
  2.2.2AWR类视图 
  第3章Buffer Cache内部原理与I/O 
  3.1HASH链表 
  3.1.1HASH链表与逻辑读 
  3.1.2Cache Buffers Chain Latch与Buffer Pin锁 
  3.1.3Cache Buffers Chain Latch的竞争 
  3.2检查点队列链表 
  3.2.1检查点队列 
  3.2.2检查点队列与实例恢复 
  3.2.3DBWR如何写脏块 
  3.2.4如何提高DBWR的写效率 
  3.3LRU队列 
  3.3.1主LRU、辅助LRU链表 
  3.3.2脏链表LRUW 
  3.3.3Free Buffer Waits 
  3.3.4谁“扣动”了DBWR的“扳机” 
  3.3.5日志切换与写脏块 
  3.4110总结 
  3.4.1逻辑读资料分析 
  3.4.2减少逻辑读——行的读取 
  3.4.3物理I/O 
  3.4.4存储物理I/O能力评估 
  第4章共享池揭密 
  4.1共享池内存结构 
  4.1.1堆、区、Chunk与子堆 
  4.1.2Chunk类型(x$ksmsp视图) 
  4.1.3freeabl、recr与LRU链表 
  4.1.4Free List链表 
  4.1.5保留池 
  4.1.6SQL的内存结构:父游标、子游标 
  4.1.7SQL的内存结构:父游标句柄 
  4.1.8SQL的Chunk:父游标堆0和DS 
  4.1.9SQL的Chunk:子游标句柄 
  4.1.10SQL的Chunk:子游标堆0与堆6 
  4.1.11SQL所占共享池内存 
  4.1.12LRU链表:我的共享池大了还是小了 
  4.1.13ORA—4031的吊诡:错误的报错信息 
  4.1.14解决ORA—4031之道:如何正确释放内存 
  4.1.15Session Cached Cursor与内存占用 
  4.2语句解析和执行 
  4.2.1SQL执行流程 
  4.2.2内存锁原理 
  4.2.3Library Cache Lock/Pin 
  4.2.4Library Cache Lock/Pin与硬解析 
  4.2.5Library Cache Lock/Pin与软解析、软软解析 
  4.2.6NULL模式Library Cache Lock与依赖链 
  4.2.7存储过程与Library Cache Lock/Pin 
  4.2.8断开依赖链 
  4.2.9低级内存锁:Latch 
  4.2.10Shared Pool Latch 
  4.3Mutex 
  4.3.1Mutex基本形式 
  4.3.2Mutex获取过程:原子指令测试并交换 
  4.3.3Mutex获取过程:竞争与Gets资料的更新 
  4.3.4Mutex获取过程:共享Mutex与独占Mutex 
  4.3.5独占Mutex的获取和释放过程 
  4.3.6Mutex获取过程:Sleeps与CPU 
  4.4Mutex与解析 
  4.4.1Mutex类型 
  4.4.2HASH Bucket与HASH链 
  4.4.3Handle(句柄)与Library Cache Lock 
  4.4.4HASH Table型Mutex 
  4.4.5执行计划与Cursor Pin 
  4.5通过Mutex判断解析问题 
  4.5.1硬解析时的竞争 
  4.5.2软解析和软软解析 
  4.5.3解决解析阶段的竞争 
  4.5.4过度软软解析竞争的解决 
  4.5.5Select与执行 
  第5章Redo调优与备份恢复原理 
  5.1非IMU与IMU Redo格式的不同 
  5.2解析Redo数据流 
  5.3IMU与非IMU相关的Redo Latch 
  5.4Redo Allocation Latch 
  5.5Log Buffer空间的使用 
  5.6LGWR与Log File Sync和Log File Parallel Write 
  5.7IMU什么情况下被使用 
  第6章UNDO 
  6.1事务基本信息 
  6.2回滚段空间重用规则 
  6.2.1UNDO块的SEQ值 
  6.2.2UNDO段的Extend 
  6.2.3Steal Undo Extent:诡异的UNDO空间不足问题 
  6.2.4回滚空间重用机制:UNDO块重用规则 
  第7章ASM 
  7.1ASM文件格式 
  7.1.1ASM文件 
  7.1.2使用kfed挖掘ASM文件格式 
  7.2AU与条带 
  7.2.1粗粒度不可调条带 
  7.2.2细粒度可调条带 
  7.2.3AU与条带的作用 
  7.2.4DG中盘数量对性能的影响 
  7.2.5最大I/O与最小I/O 
  7.2.6数据分布对性能的影响 
  7.2.7案例精选:奇怪的IO问题 
  7.2.8大AU和小AU性能对比 
  7.2.9AU与条带总结 
  7.2.10 OLTP与大条带 
  附录HASH算法简单介绍
========

Oracle中IO



数据库的作用就是实现对数据的管理和查询。任何一个数据库系统,必然存在对数据的大量读或者写或者两中


操作都大量存在。IO问题也往往是导致数据库性能问题的重要原因。在这篇文章中,主要帮助大家在理解


Oracle的读写操作机制的基础上,灵活解决遇到的各种常见的IO问题。


1 Oracle中IO的产生
IO当然包括了读、写两部分,先介绍Oracle中写操作的产生。


1.1 写
介绍写操作之前,先简单的看下Oracle的物理结构:oracle的物理文件包括以下三种文件:控制文件(Control 


Files)、重做日志文件(Redo Log Files)、数据文件(datafiles)。而数据文件中,根据功能的不同,还


可以分为系统数据文件、临时空间文件、回滚段文件和用户数据文件。另外,如果数据库的Archive Log模式被


激活,还存在归档日志文件。Oracle的IO产生,就是对这些文件的数据读、写操作。下面再详细看下几种主要


写操作的产生及其过程。


1.1.1 控制文件
控制文件中记录了整个数据库的物理结构信息,如数据库名字、数据文件及日志文件名字和位置、事件戳信息


等等。任何数据库的结构变化(如果创建新的数据文件)都会引起Oracle修改控制文件。同时控制文件还记录


系统和各个数据文件的SCN(System Change Number,关于SCN可以参见文章《Oracle SCN机制详解》)信息,


以用于数据恢复,因此数据文件上的SCN变化后,Oracle也会相应修改控制文件上的SCN信息。


1.1.2 用户数据修改
由于内存的读写效率比磁盘的读写效率高万倍,因此,为了降低IO wait,oracle会将数据cache在内存


(Buffer Cache,对Buffer Cache的详细介绍可以参见《Oracle内存全面分析》)中,对数据的读写尽量在内


存中完成。当Buffer Cache中的数据缓存块被修改过了,它就被标记为“脏”数据。根据LRU(Least Recently 


Used)算法,如果一个数据块最近很少被使用,它就称为“冷”数据块。进程DBWn(系统中可以存在多个DBW进


程,n为序号)负责将“冷”的“脏”数据写入数据文件中去。DBWn进程会在以下两种情况下将“脏”数据写入


磁盘中去:


当服务进程扫描一定数量(阀值)的Buffer Cache后还没有找到干净、可重用的缓存块后,它会通知DBWn进程


将“脏”数据写入文件中去,以释放出空闲缓存;
当发生检查点(Checkpoint)时。
1.1.3 Redo Log
在非直接写(Direct Write)的情况下,事务中的写操作都会产生Redo Log,作为数据块异常关闭时的恢复记


录。同样,和写用户数据类似,Redo Log也不会被直接写入Redo Log文件,而是先写入Log Buffer中。


Log Buffer是一个可以循环重用的缓存区。LGWR进程负责将Log Buffer中的记录写入Redo Log File中去。一旦


Log Buffer中的条目被写入了Redo Log文件中,就可以被重用了。


为了保证事务尽快获得Log Buffer,LGWR进程一般会尽快将Log Buffer中的数据写入Redo Log文件中去。在以


下几种情况下,LGWR回将一个连续的Log Buffer写入Redo Log文件中去:


当一个事务提交(COMMIT)时;
每3秒钟写一次Log Buffer;
当Log Buffer到达1/3满时;
当DBWn进程将“脏”数据写入磁盘时;
1.1.4 Archive Log
当据库的Archive Log模式被激活后,所有Redo Log数据都会被写入Archive Log文件中以便日后进行恢复。当


发生日志组切换时,ARCn(Archive进程,可以存在多个)进程就会Redo Log文件拷贝到指定存储目录中去,成


为Archive Log文件。


1.1.5 临时表空间
当Oracle在执行一些SQL时,会需要一些临时空间来存储执行语句时产生的中间数据。这些临时空间由Oracle从


指定的临时表空间中分配给进程。主要有三种情况会占用临时空间:临时表/索引操作、排序和临时LOB操作。


临时表/索引
在会话中,当第一次对临时表进行INSERT(包括CTAS)时,Oracle会从临时表空间中为临时表及其索引分配临


时空间一存储数据。


排序
任何会使用到排序的操作,包括JOIN、创建(重建)INDEX、ORDER BY、聚合计算(GROUP BY)以及统计数据收


集,都可能使用到临时表空间。


排序操作首先会选择在内存中的Sort Area进行(Sort In Memory),一旦Sort Area不足,则会使用临时空间


进行排序操作(Sort In Disk)。看以下例子:


SQL> alter session set sort_area_size = 10000000;
 
Session altered.
 
SQL> select owner, object_name from t_test1
  2  order by object_id;
 
47582 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 1312425564
 
------------------------------------------------------------------------------
| Id  | Operation  | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   | | 47582 |  1486K|   155   (4)| 00:00:02 |
|   1 |  SORT ORDER BY     | | 47582 |  1486K|   155   (4)| 00:00:02 |
|   2 |   TABLE ACCESS FULL| T_TEST1 | 47582 |  1486K|   150   (1)| 00:00:02 |
------------------------------------------------------------------------------
 
 
Statistics
----------------------------------------------------------
  1  recursive calls
  0  db block gets
   658  consistent gets
  0  physical reads
  0  redo size
    1566184  bytes sent via SQL*Net to client
 35277  bytes received via SQL*Net from client
  3174  SQL*Net roundtrips to/from client
  1  sorts (memory)
  0  sorts (disk)
 47582  rows processed
 
SQL> alter session set sort_area_size = 10000;
 
Session altered.
 
SQL> select owner, object_name from t_test1
  2  order by object_id;
 
47582 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 1312425564
 
--------------------------------------------------------------------------------
| Id  | Operation  | Name    | Rows  | Bytes |TempSpc| Cost (%CPU)| Time|
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   | | 47582 |  1486K|  |  1251   (1)| 00:0
0:16 |
|   1 |  SORT ORDER BY     | | 47582 |  1486K|  4136K|  1251   (1)| 00:0
0:16 |
|   2 |   TABLE ACCESS FULL| T_TEST1 | 47582 |  1486K|  |   150   (1)| 00:0
0:02 |
 
---------------------------------------------------------------------------------
 
Statistics
----------------------------------------------------------
  6  recursive calls
 20  db block gets
   658  consistent gets
   629  physical reads
  0  redo size
    1566184  bytes sent via SQL*Net to client
 35277  bytes received via SQL*Net from client
  3174  SQL*Net roundtrips to/from client
  0  sorts (memory)
  1  sorts (disk)
 47582  rows processed
临时LOB对象
LOB对象包括BLOB、CLOB、NCLOB、和BFILE。在PLSQL程序块中,如果定义了LOB变量,则这些LOB变量就是临时


LOB对象。临时LOB对象被创建在临时表空间上,直到LOB数据被释放,或者会话结束。


1.1.6 回滚段
我们知道,一个事务在未被提交前,其做的任何修改都是可以被回滚(Rollback)的。这些回滚数据就被放到


回滚段(Rollback Segment)上。此外,一致性读(Read Consistency)、数据库恢复(Recover)都会用到回


滚段。


任何数据块的修改都会被记录在回滚段中,甚至Redo Log也会产生回滚记录。当任何一个非只读(只有查询)


的事务开始时,oracle会自动为其指定下一个可用的回滚段。事务中任何数据变化都被写入回滚段中。如果事


务回滚,oracle根据回滚段中的回滚记录将buffer cache中的“脏”数据恢复,释放回滚段空间。当事务被提


交,由于要保证一致性读,oracle并不会立即释放回滚段中的数据,而是会保留一段时间。


1.1.7 Direct-Path Insert
这里,我们还要介绍一种特殊的写操作——Direct-Path Insert(直接路径插入)。Direct-Path Insert通过


直接在表中已存在的数据后面添加数据,直接将数据写入数据文件中,而忽略掉了Buffer Cache。


我们前面提到,为了能在意外时恢复数据,每一个数据修改都会被记录到Redo Log中。然而,由于Redo Log需


要写入到物理文件中去,是一个比较消耗性能的操作。为了提高性能,我们在批量写入数据时就可以通过


Direct-Path Insert的指定NOLOGING的方式来避免写Redo Log。


有多种方法可以指定Direct-Path Insert:CTAS(CREATE TABLE AS SELECT);SQL*Loader指定Direct参数;


在语句中指定APPEND提示。


1.2     读
1.2.1 物理读
产生物理读主要有以下几种情况:


第一次读取
当数据块第一次被读取到,Oracle会先将其从磁盘上读入Buffer Cache中,并将他们放在LRU(Last Recently 


Used)链表的MRU(Most Recently Used)端。再次访问数据块时就可以直接从Buffer Cache中读取、修改了。


看以下例子:


SQL> select owner, index_name from t_test3;
 
2856 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2878488296
 
-----------------------------------------------------------------------------
| Id  | Operation | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT  | |  2856 | 68544 |    22   (0)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| T_TEST3 |  2856 | 68544 |    22   (0)| 00:00:01 |
-----------------------------------------------------------------------------
 
 
Statistics
----------------------------------------------------------
   407  recursive calls
 32  db block gets
   344  consistent gets
89  physical reads
  0  redo size
     103888  bytes sent via SQL*Net to client
  2475  bytes received via SQL*Net from client
   192  SQL*Net roundtrips to/from client
  9  sorts (memory)
  0  sorts (disk)
  2856  rows processed
 
SQL> select owner, index_name from t_test3;
 
2856 rows selected.
 
Elapsed: 00:00:00.03
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2878488296
 
-----------------------------------------------------------------------------
| Id  | Operation | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT  | |  2856 | 68544 |    22   (0)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| T_TEST3 |  2856 | 68544 |    22   (0)| 00:00:01 |
-----------------------------------------------------------------------------
 
 
Statistics
----------------------------------------------------------
  0  recursive calls
  0  db block gets
   276  consistent gets
  0  physical reads
  0  redo size
     103888  bytes sent via SQL*Net to client
  2475  bytes received via SQL*Net from client
   192  SQL*Net roundtrips to/from client
  0  sorts (memory)
  0  sorts (disk)
  2856  rows processed
数据块被重新读入Buffer Cache
如果有新的数据需要被读入Buffer Cache中,而Buffer Cache又没有足够的空闲空间,Oracle就根据LRU算法将


LRU链表中LRU端的数据置换出去。当这些数据被再次访问到时,需要重新从磁盘读入。


SQL> select owner, table_name from t_test2
  2  where owner = 'SYS';
 
718 rows selected.
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 1900296288
--------------------------------------------------------------------------------
| Id  | Operation  | Name | Rows  | Bytes | Cost (%CPU)
| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    | |    99 |  2178 |    10   (0)
| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T_TEST2 |    99 |  2178 |    10   (0)
| 00:00:01 |
|*  2 |   INDEX RANGE SCAN  | T_TEST2_IDX1 |    99 |  |     1   (0)
| 00:00:01 |
--------------------------------------------------------------------------------
 
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("OWNER"='SYS')
 
 
Statistics
----------------------------------------------------------
  0  recursive calls
  0  db block gets
   145  consistent gets
  0  physical reads
  0  redo size
 21690  bytes sent via SQL*Net to client
   902  bytes received via SQL*Net from client
 49  SQL*Net roundtrips to/from client
  0  sorts (memory)
  0  sorts (disk)
   718  rows processed
 
SQL> select * from t_test1; --占用Buffer Cache
 
47582 rows selected.
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 1883417357
 
-----------------------------------------------------------------------------
| Id  | Operation | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT  | | 47582 |  3996K|   151   (2)| 00:00:02 |
|   1 |  TABLE ACCESS FULL| T_TEST1 | 47582 |  3996K|   151   (2)| 00:00:02 |
-----------------------------------------------------------------------------
 
Statistics
----------------------------------------------------------
   195  recursive calls
  0  db block gets
  3835  consistent gets
  5  physical reads
  0  redo size
    5102247  bytes sent via SQL*Net to client
 35277  bytes received via SQL*Net from client
  3174  SQL*Net roundtrips to/from client
  5  sorts (memory)
  0  sorts (disk)
47582  rows processed
 
SQL> select owner, table_name from t_test2
  2  where owner = 'SYS';
 
718 rows selected.
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 1900296288
 
--------------------------------------------------------------------------------
| Id  | Operation   | Name | Rows  | Bytes | Cost (%CPU)
| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    | |    99 |  2178 |    10   (0)
| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T_TEST2 |    99 |  2178 |    10   (0)
| 00:00:01 |
|*  2 |   INDEX RANGE SCAN  | T_TEST2_IDX1 |    99 |  |     1   (0)
| 00:00:01 |
--------------------------------------------------------------------------------
 
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("OWNER"='SYS')
 
 
Statistics
----------------------------------------------------------
  0  recursive calls
  0  db block gets
   145  consistent gets
 54  physical reads
  0  redo size
 21690  bytes sent via SQL*Net to client
   902  bytes received via SQL*Net from client
 49  SQL*Net roundtrips to/from client
  0  sorts (memory)
  0  sorts (disk)
   718  rows processed
全表扫描
当发生全表扫描(Full Table Scan)时,用户进程读取表的数据块,并将他们放在LRU链表的LRU端(和上面不


同,不是放在MRU端)。这样做的目的是为了使全表扫描的数据尽快被移出。因为全表扫描一般发生的频率较低


,并且全表扫描的数据块大部分在以后都不会被经常使用到。


而如果你希望全表扫描的数据能被cache住,使之在扫描时放在MRU端,可以通过在创建或修改表(或簇)时,


指定CACHE参数。


1.2.2 逻辑读
逻辑读指的就是从(或者视图从)Buffer Cache中读取数据块。按照访问数据块的模式不同,可以分为即时读


(Current Read)和一致性读(Consistent Read)。注意:逻辑IO只有逻辑读,没有逻辑写。


即时读
即时读即读取数据块当前的最新数据。任何时候在Buffer Cache中都只有一份当前数据块。即时读通常发生在


对数据进行修改、删除操作时。这时,进程会给数据加上行级锁,并且标识数据为“脏”数据。


SQL> select * from t_test1 where owner='SYS' for update;
 
22858 rows selected.
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 3323170753
 
------------------------------------------------------------------------------
| Id  | Operation  | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   | | 22858 |  1919K|   151   (2)| 00:00:02 |
|   1 |  FOR UPDATE   | |  |  |    |  |
|*  2 |   TABLE ACCESS FULL| T_TEST1 | 22858 |  1919K|   151   (2)| 00:00:02 |
------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - filter("OWNER"='SYS')
 
 
Statistics
----------------------------------------------------------
 44  recursive calls
 23386  db block gets
  2833  consistent gets
  0  physical reads
    5044956  redo size
    2029221  bytes sent via SQL*Net to client
 17138  bytes received via SQL*Net from client
  1525  SQL*Net roundtrips to/from client
  0  sorts (memory)
  0  sorts (disk)
 22858  rows processed
一致性读
Oracle是一个多用户系统。当一个会话开始读取数据还未结束读取之前,可能会有其他会话修改它将要读取的


数据。如果会话读取到修改后的数据,就会造成数据的不一致。一致性读就是为了保证数据的一致性。在


Buffer Cache中的数据块上都会有最后一次修改数据块时的SCN。如果一个事务需要修改数据块中数据,会先在


回滚段中保存一份修改前数据和SCN的数据块,然后再更新Buffer Cache中的数据块的数据及其SCN,并标识其


为“脏”数据。当其他进程读取数据块时,会先比较数据块上的SCN和自己的SCN。如果数据块上的SCN小于等于


进程本身的SCN,则直接读取数据块上的数据;如果数据块上的SCN大于进程本身的SCN,则会从回滚段中找出修


改前的数据块读取数据。通常,普通查询都是一致性读。


下面这个例子帮助大家理解一下一致性读:


会话1中:


SQL> select object_name from t_test1 where object_id = 66;
 
OBJECT_NAME
------------------------------
I_SUPEROBJ1
 
SQL> update t_test1 set object_name = 'TEST' where object_id = 66;
 
1 row updated.
会话2中:


SQL> select object_name from t_test1 where object_id = 66;
 
OBJECT_NAME
------------------------------
I_SUPEROBJ1
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 1883417357
 
-----------------------------------------------------------------------------
| Id  | Operation | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT  | |     1 |    27 |   151   (2)| 00:00:02 |
|*  1 |  TABLE ACCESS FULL| T_TEST1 |     1 |    27 |   151   (2)| 00:00:02 |
-----------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("OBJECT_ID"=66)
 
 
Statistics
----------------------------------------------------------
  0  recursive calls
  0  db block gets
   661  consistent gets
  0  physical reads
   108  redo size
   423  bytes sent via SQL*Net to client
   385  bytes received via SQL*Net from client
  2  SQL*Net roundtrips to/from client
  0  sorts (memory)
  0  sorts (disk)
  1  rows processed
1.2.3 查找数据
在一个查询操作中,大量的读操作都产生于数据的查找过程中。减少查找过程是我们优化IO性能问题的重要目


标。


下面介绍几种主要的数据查找方式。


Full Table Scan
当查询条件无法命中任何索引、或者扫描索引的代价大于全表扫描代价的某一比例时(由参数


optimizer_index_cost_adj设定),Oracle会采用全表扫描的方式查找数据。当发生全表扫描时,Oracle会自下


向上一次读取一定数量(由参数db_file_multiblock_read_count设定)的数据块,一直读取到高水位标志


(HWM,High Water Mark)下。Full Table Scan会引起db file scattered read事件。


INDEX UNIQUE SCAN
全表扫描查找数据的效率是非常低的。而索引能大幅提高查找效率。普通索引的数据结构是B-Tree,树的叶子


节点中包含数据的ROWID,指向数据记录,同时还有指针指向前一个/后一个叶子节点。索引扫描每次读取一个


数据块,索引扫描是“连续的”(Sequential)。当索引为UNIQUE索引时,每个叶子节点只会指向一条数据。


如果Oracle能预知扫描结果只有0或1条记录时,会采用INDEX UNIQUE SCAN。当对Unique Index中的所有字段进


行完全匹配时,会发生INDEX UNIQUE SCAN。


SQL> select object_name from t_test1
  2  where object_id = 66;
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2634232531
 
---------------------------------------------------------------------------------
| Id  | Operation   | Name  | Rows  | Bytes | Cost (%CPU)|
Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |    |     1 |    27 |     1   (0)|
00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T_TEST1    |     1 |    27 |     1   (0)|
00:00:01 |
|*  2 |   INDEX UNIQUE SCAN | T_TEST1_PK |     1 |  |     1   (0)|
00:00:01 |
---------------------------------------------------------------------------------
INDEX UNIQUE SCAN的查找过程如下:


从数的根节点数据块开始查找;
查找根节点块中所有key值中大于或等于要查找的值的最小key值;
如果key值大于查找值,则继续查找这个key值之前一个key值所指向的子节点数据块;
如果key值等于查找值,则继续查找这个key值所指向的子节点数据块;
如果没有key值大于或等于查找值,则继续查找最大key值所指向的子节点数据块;
如果继续查找的节点数据块是数一个分支节点,则重复2~4步;
如果查找的节点是叶子节点数据块,则在数据块中查找等于查找值的key值;
如果找到相等的key值,则返回数据和ROWID;
如果没找到相等的key值,则说明没有符合条件的数据,返回NULL。
INDEX RANGE SCAN
如果通过索引查找数据时,Oracle认为会返回数据可能会大于1,会进行INDEX RANGE SCAN,例如Unique Index


中字段不完全匹配查找时、非Unique Index查找时。


SQL> select object_name from t_test1
  2  where object_id < 66;
 
64 rows selected.
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 1635545337
 
---------------------------------------------------------------------------------
| Id  | Operation   | Name  | Rows  | Bytes | Cost (%CPU)|
Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |    |    57 |  1539 |     2   (0)|
00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T_TEST1    |    57 |  1539 |     2   (0)|
00:00:01 |
|*  2 |   INDEX RANGE SCAN  | T_TEST1_PK |    57 |  |     1   (0)|
00:00:01 |
---------------------------------------------------------------------------------
INDEX RANGE SCAN分为闭包(有前后查找边界)和非闭包(只有一边或者没有边界)。返回数据会依据索引增


序排序,多个相同值则会按照ROWID的增序排序。以下的查找条件都是闭包的:


WHERE column = 'Value'
WHERE column like 'value%'
WHERE column between 'value1' and 'value2'
WHERE column in ('value1', 'value2')
以下查找条件非闭包:


WHERE column < 'value1'
WHERE column > 'value2'
闭包条件下的INDEX RANGE SCAN的查找过程如下:


从数的根节点数据块开始查找;
查找根节点块中所有key值中大于或等于要查找的起始值的最小key值;
如果key值大于起始值,则继续查找这个key值之前一个key值所指向的子节点数据块;
如果key值等于起始值,则继续查找这个key值所指向的子节点数据块;
如果没有key值大于或等于起始值,则继续查找最大key值所指向的子节点数据块;
如果继续查找的节点数据块是数一个分支节点,则重复2~4步;
如果查找的节点是叶子节点数据块,则在数据块中大于或等于要查找的起始值的最小key值;
如果Key值小于或等于结束值,则:如果所有Key字段都符合WHERE字句中的查找条件,则返回数据和ROWID;否


则继续查找当前叶子节点所指向的右边的叶子节点。
INDEX UNIQUE SCAN和INDEX RANGE SCAN都会引起db file sequential read事件。


TABLE ACCESS BY INDEX ROWID
当发生索引扫描时,如果需要返回的字段都在索引上,则直接返回索引上的数据,而如果还需要返回非索引上


的字段的值,Oracle则需要根据从索引上查找的ROWID到对应的数据块上取回数据,这时就是TABLE ACCESS BY 


INDEX ROWID。


INDEX FAST FULL SCAN & INDEX FULL SCAN
索引快速全扫描和全表扫描类似,一次读取db_file_multiblock_read_count个数据块来描所有索引的叶子节点


。INDEX FAST FULL SCAN和其他索引扫描不同,它不会从树的根节点开始读取,而是直接扫描所有叶子节点;


也不会一次读取一个数据块,而是一次读取db_file_multiblock_read_count个数据块。INDEX FAST FULL SCAN


会引起db file scattered read事件。


SQL> select count(1) from t_test1 where object_id < 21314;
 
Execution Plan
----------------------------------------------------------
Plan hash value: 1586700957
 
---------------------------------------------------------------------------------
| Id  | Operation     | Name  | Rows | Bytes| Cost (%CPU)| Time   |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |    |     1|     4|    24   (5)| 00:00:01|
|   1 |  SORT AGGREGATE  |    |     1|     4|    ||
|*  2 |   INDEX FAST FULL SCAN| T_TEST1_PK | 18264| 73056|    24   (5)| 00:00:01|
---------------------------------------------------------------------------------
在某些情况下,如db_file_multiblock_read_count值过小、强制使用索引扫描时,会发生INDEX FULL SCAN。


INDEX FULL SCAN和INDEX FAST FULL SCAN不同,它是一种索引扫描,按照B-Tree的查找法从树的根节点开始扫


描,遍历整棵树,并且一次读取一个数据块。它会引起db file sequential read事件。


SQL> select /*+index(a t_test1_pk)*/count(1) from t_test1 a;
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 138350774
 
-----------------------------------------------------------------------
| Id  | Operation   | Name  | Rows  | Cost (%CPU)| Time     |
-----------------------------------------------------------------------
|   0 | SELECT STATEMENT |    |     1 |    61   (2)| 00:00:01 |
|   1 |  SORT AGGREGATE  |    |     1 |    |  |
|   2 |   INDEX FULL SCAN| T_TEST1_PK | 47582 |    61   (2)| 00:00:01 |
-----------------------------------------------------------------------
2 IO系统的设计和配置
要控制好数据库的整体IO性能,在规划数据库架构时就需要做好IO系统的设计和配置。例如,将对IO要求不同


的文件放置在不同的存储设备上;规划数据文件的分布、均衡IO负担等。


2.1     OS和存储相关
IO性能是直接和操作系统已经硬件性能相关的。如果能利用操作系统的一些高级IO特性,或者采用更高速的磁


盘设备,能大大提高IO性能。下面介绍一些OS的IO配置、不同的磁盘硬件设备以及存储技术。


2.1.1 文件系统(File System)和裸设备(Raw Device)
我们知道,内存的读写效率比磁盘高近万倍,因此Oracle在内存中开辟了一片区域,称为Buffer Cache,使数


据的读写尽量在Buffer Cache中完成。同样,在文件系统中,操作系统为了提高读写效率,也会为文件系统开


辟一块Buffer Cache用于读写数据的缓存。这样,Oracle的数据会被缓存2次。为了避免OS的这次缓存,我们可


以采用裸设备做为数据文件的存储设备。裸设备,也称为裸分区(Raw Partition),它是一个没有被加载


(Mount)到操作系统的文件系统上、也没有加载到Oracle集群文件系统(OCFS Oracle Cluster File System


)的磁盘分区,它通过字符设备驱动来访问。裸设备的文件读写不由操作系统控制,而是由应用程序(如


Oracle RDBMS)直接控制。


2.1.2 IO方式
OS和文件系统对IO的控制存在多种方式,不同的IO方式下对于数据库的IO性能影响也不同。


2.1.2.1  Direct IO & Concurrent IO
除了裸设备,某些文件系统可以支持Direct IO,以避开读写缓冲。如果要使用Direct IO,需要指定Oracle参


数“filesystemio_options”来设置支持Direct IO。但是要注意,不同OS中的不同文件系统对Direct IO的支


持也不同:


Windows     在windows中不需要做特别设置可以直接使用Direct IO;
AIX    在AIX中,JFS文件系统需要通过设置“filesystemio_options”为“SETALL”或者“DIRECTIO”来支持


Direct IO;
LINUX Linux在内核版本为2.4.9以上才支持Direct IO。NFS或者OCFS文件系统支持Direct IO。需要设


置“filesystemio_options”为“SETALL”或者“DIRECTIO”;
Solaris     Solaris需要在操作系统中设置“forcedirectio”选项,并设


置“filesystemio_options”为“SETALL”或者“DIRECTIO”。
参数“filesystemio_options”支持4种值:


ASYNCH: 使Oracle支持文件的异步(Asynchronous)IO;
DIRECTIO:使Oracle支持文件的Direct IO;
SETALL:使Oracle同时支持文件的Asynchronous IO和Direct IO;
NONE:使Oracle关闭对Asynchronous IO和Direct IO的支持。
在AIX的JFS2文件系统上,如果“filesystemio_options”为“SETALL”,则会支持Concurrent IO。CIO比DIO


的性能更高,因为JFS2的CIO支持多个进程同时对一个文件进行读写。


2.1.2.2  Asynchronous IO & Synchronous IO
通常,用的比较多的IO模型是同步IO(Synchronous IO)。在这种模式下,当请求发出之后,应用程序就会阻


塞,直到请求满足为止。这种模式最大好处就是调用应用程序在等待 I/O 请求完成时不需要使用CPU资源。但


是,对于一些强调高响应速度的程序(如DB)来说,希望这种等待时间越短越好,我们这时就可以考虑采用异


步IO(Asynchronous IO)模式。异步IO模式下,进程发出IO请求后无需等待IO完成,可以去处理其它事情;IO


请求被放入一个队列中,一旦IO完成,系统会发出信号通知进程。


异步IO可以使需要大量写的Oracle进程(如DBWn进程)将IO请求队列化,以充分利用硬件的IO带宽,从而使它


们能最大程度实现并行处理。异步IO还可以使那些需要进行大量计算的操作(如排序)在它们发出IO请求前预


先从磁盘取出数据,以使IO和计算并行处理。


确认操作系统已经设置支持AIO后,还需要设置Oracle初始化参数"DISK_ASYNCH_IO"为“true”以支持异步IO。


2.1.3 负载均衡及条带化(Striping)
当多个进程同时访问一个磁盘时,会出现磁盘冲突。大多数磁盘系统都对访问次数(每秒的IO操作)和数据传


输率(每秒传输的数据量)有限制。当达到这些限制时,后面要访问磁盘的进程就需要等待,这时就是所谓的


磁盘冲突。


避免磁盘冲突是优化IO性能的一个目标,这就需要将一个热点磁盘上的IO访问负载分担到其他可用磁盘上,也


就是IO负载均衡。在一些成熟的磁盘负载均衡技术出现之前,DBA需要了解/预测各系统的IO负载量,通过手工


配置每个数据到不同存放位置以分担IO负载来达到负载均衡的目的。


条带化技术就是将数据分成很多小部分并把他们分别存储到不同磁盘上的不同文件中去。这就能使多个进程同


时访问数据的多个不同部分而不会造成磁盘冲突。很多操作系统、磁盘设备供应商、各种第三方软件都能做到


条带化。通过条带化,DBA可以很轻松的做到IO负载均衡而无需去手工配置。


2.1.4 RAID
RAID的全称是独立磁盘冗余阵列(Redundant Array of Independent Disks)。它通过将多个相对比较便宜的


磁盘组合起来,并相互连接,同时都连到一个或多个计算机上,以组成一个磁盘组,使其性能和容量达到或超


过一个价格更昂贵的大型磁盘。RAID分为6级。


RAID-0
RAID-0只提供纯粹的条带化(Stripping)。条带可以使一个大文件被多个磁盘控制器同时访问,因此支持对数


据的并发访问。RAID-0不提供数据冗余和奇偶保护,它只关注性能。如果RAID-0中任何一个磁盘出错,整个数


据库都会崩溃。


RAID-1
RAID-1提供磁盘镜像(Disk Mirror)。在RAID-1中,所有数据都会被写入两个独立的磁盘中,以实现对数据的


冗余保护。两块磁盘的数据是同时写入的,以保证其速度不会低于写入单独磁盘的速度。RAID-1实现了数据的


完全冗余,它提供了所有RAID级别中最安全可靠的数据保护。在这种模式下,写的性能下降了,但读的性能被


提升了。此外,RAID-1也是最占用磁盘空间的模式


RAID 0+1
RAID-0能提供更好的性能,RAID-1提供最佳的数据保护。如果把两者结合在一起就能同时提供高性能和数据保


护,但是也会同时提高磁盘阵列造价。


RAID-3
在RAID-3中,会有一块专门的磁盘驱动被用作存储错误修正或者奇偶校验数据。而其他的磁盘驱动则被条带化


。RAID-3的并行处理能力比较低,它适合于主要是读操作的系统(如决策分析系统 DSS,但是DSS会存在大量复


杂查询,需要做JOIN,同样也会存在一些临时的写操作),不适合存在大量写操作的系统(OLTP)。


RAID-5
RAID-5不做全磁盘镜像,但它会对每一个写操作做奇偶校验计算并写入奇偶校验数据。奇偶校验磁盘避免了像


RAID-1那样完全重复写数据。当一个磁盘失效,校验数据被用来重建数据,从而保证系统不会崩溃。为避免磁


盘瓶颈,奇偶校验和数据都会被分布到阵列中的各个磁盘。尽管读的效率提高了,但是RAID-5需要为每个写操


作做奇偶校验,因此它的写的效率很差。


RAID-S
RAID-S是EMC公司的RAID-5的实施方案,它和纯粹的RAID-5存在以下区别:


(1) 它条带化奇偶校验,但不条带化数据;


(2) 它与一个带有写缓存的异步硬件环境合并。


这个缓存主要是一种延迟写的机制,因此它能让系统在相对不忙的时候计算和写奇偶校验信息。


RAID-7
RAID-7也同样引入了缓存机制,这个缓存是被一个内嵌式操作系统控制。但是,RAID-7中数据是被条带化的,


而奇偶校验不被条带化。奇偶校验信息被存放着一个或者多个专门的磁盘上。


2.1.5 SAN
SAN(Storage Area Network,存储区域网)是一个高速的子网,这个子网中的设备可以从你的主网卸载流量。


通常SAN由RAID阵列连接光纤通道(Fibre Channel)组成,SAN和服务器和客户机的数据通信通过SCSI命令而非


TCP/IP,数据处理是“块级”(block level)。


SAN通过特定的互连方式连接的若干台存储服务器组成一个单独的数据网络,提供企业级的数据存储服务。 SAN


是一种特殊的高速网络,连接网络服务器和诸如大磁盘阵列或备份磁带库的存储设备,SAN置于LAN之下,而不


涉及LAN。利用SAN,不仅可以提供大容量的存储数据,而且地域上可以分散,并缓解了大量数据传输对于局域


网的影响。SAN的结构允许任何服务器连接到任何存储阵列,不管数据置放在哪里,服务器都可直接存取所需的


数据。


2.1.6 NAS
NAS是Network Attached Storage(网络附加存储)的简称。在NAS存储结构中,存储系统不再通过I/O总线附属


于某个服务器或客户机,而直接通过网络接口与网络直接相连,由用户通过网络访问。它是连接到一个计算机


网络的文件层的数据存储,它可以为不同网络客户端提供数据存储服务。NAS的硬件与传统的专用文件服务器相


似。它们的不同点在于软件端。NAS中的操作系统和其他软件只提供数据存储、数据访问功能,以及对这些功能


的管理。与传统以服务器为中心的存储系统相比,数据不再通过服务器内存转发,直接在客户机和存储设备间


传送,服务器仅起控制管理的作用。


2.2     IO配置
在借助各种成熟的存储技术的基础上,合理配置系统的IO分布及系统IO配置能大量减少系统在生产运行中出现


IO性能及相关问题的几率。当然,这些配置是我们在布置数据库系统时初始建议,对于复杂的系统来说,很多


配置(如一些存储相关的参数)是需要根据系统的运行状况进行调优的。


在数据库系统中,如果某个文件或者某块磁盘上存在远远高于其他文件或磁盘的大量IO访问,我们就称这个文


件或磁盘为热点文件/磁盘。我们在做IO规划时的一个重要目标就是要消除系统中热点文件/磁盘的存在,使整


个系统的IO负载相对平衡。


2.2.1 条带化的设置
由于现在的存储技术成熟、成本降低,大多数系统都采用条带化来实现系统的IO负载分担。如果操作系统有LVM


(Logical Volume Manager逻辑卷管理器)软件或者硬件条带设备,我们就可以利用这些攻击来分布IO负载。


当使用LVM或者硬件条带时,决定因素是条带深度(stripe depth)和条带宽度(stripe width):


条带深度指的是条带的大小,也叫条带单元;
条带宽度指的是条带深度的产量或者一个条带集中的驱动数;
需要根据系统的IO要求来合理的选择这些数据。对于Oracle数据库系统来数,比较合理的条带深度是从256K到


1M。下面分析影响条带深度和条带宽度的影响因素。


2.2.1.1  条带深度
为了提高IO效率,我们要尽量使一次逻辑IO请求由一块磁盘的一次物理IO请求。因而影响条带的一个重要因素


就是一次逻辑IO请求的大小。


此外,系统中IO的并发度不同我们对条带的配置要求也不同。例如,在高并发度且IO请求的大小都比较小的情


况下,我们希望一块磁盘能同时响应多个IO操作;而在那些存在大IO请求的低并发度系统中,我们可能就需要


多块磁盘同时响应一个IO请求。无论是一个磁盘还是多个磁盘响应IO请求,我们的一个原则是让一次逻辑IO能


被一次处理完成。


下面先看下影响IO大小的操作系统和Oracle的相关参数:


db_block_size:Oracle中的数据块大小,也决定了Oracle一次单个IO请求中的数据块的大小;
db_file_multiblock_read_count:在多数据块读时,一次读取数据块的数量,它和参数db_block_size一起决


定了一次多数据块读的大小,它们的乘积不能大于操作系统的最大IO大小;
操作系统的数据块大小:这个参数决定拉Redo Log和Archive Log操作时的数据块大小,对于大多数Unix系统来


说,该值为512K;
最大操作系统IO大小:决定了一次单个的IO操作的IO大小的上限,对于大多数Unix系统来说,由参数


max_io_size设置;
sort_area_size:内存中sort area的大小,也决定了并发排序操作时的IO大小;
hash_area_size:内存中hash area的大小,也决定了哈希操作的IO大小。
其中,前面两个是最关键的两个参数。


在OLTP系统中,会存在大量小的并发的IO请求。这时就需要考虑选择比较大的条带深度。使条带深度大于IO大


小就称为粗粒度条带(Coarse Grain Striping)。在高并行度系统中,条带深度为(n * db_block_size),


其中n为大于1的整数。


通过粗粒度条带能实现最大的IO吞吐量(一次物理IO可以同时响应多个并发的逻辑IO)。大的条带深度能够使


像全表扫描那样的多数据块读操作由一个磁盘驱动来响应,并提高多数据块读操作的性能。


在低并发度的DSS系统中,由于IO请求比较序列化,为了避免出现热点磁盘,我们需要避免逻辑IO之由一块磁盘


处理。这是,粗粒度条带就不适合了。我们选择小的条带深度,使一个逻辑IO分布到多个磁盘上,从而实现IO


的负载均衡。这就叫细粒度条带。条带深度的大小为(n * db_block_size),其中n为小于多数据块读参数


(db_file_multiblock_read_count)大小的整数。


另外,IO过程中,你无法保证Oracle数据块的边界能和条带单元的大小对齐。如果条带深度大小和Oracle数据


块大小完全相同,而它们的边界没有对齐的话,那么就会存在大量一个单独的IO请求被两块磁盘来完成。


在OLTP系统中,为了避免一个逻辑IO请求被多个物理IO操作完成,条带宽度就需要设置为两倍或者两倍以上于


Oracle数据块大小。例如,如果条带深度是IO大小的N倍,对于大量并发IO请求,我们可以保证最少有(N-1)/ 


N的请求是由一块磁盘来完成。


2.2.1.2  条带宽度
正如我们前面所述,无论是一个还是多个磁盘响应一个逻辑IO,我们都要求IO能被一次处理。因而在确定了条


带深度的基础上,我们需要保证条带宽度 >= IO请求的大小 / 条带深度。


此外,考虑到以后系统容量的扩充,我们也需要规划好条带宽度。


如今大多数LVM都支持在线动态增加磁盘。也就是在磁盘容量不足时,我们可以随时将新磁盘加入到一个已经使


用的逻辑卷中。这样的话,我们在设置逻辑卷时就可以简单地将所有磁盘都归入到一个卷中去。


但是,有些LVM可能还不支持动态增加磁盘。这时我们就需要考虑以后的容量扩充对IO均衡的影响了。因为你新


增加的磁盘无法加入原有卷,而需要组成一个新的卷。但一般扩充的容量和原有容量比较相对比较小,如果原


有卷的条带宽度比较大的话,新增加的卷的条带宽度无法达到其大小,这样就会使新、旧卷之间出现IO失衡。


例如,一个系统的初始配置是一个包含64块磁盘、每块磁盘大小为16G的单一逻辑卷。磁盘总的大小是1T。随着


数据库的数据增长,需要增加80G的空间。我们把新增加的5个16G磁盘再组成一个逻辑卷。这样就会导致两个卷


上的IO失衡。为了避免这种情况。我们可以将原有磁盘配置成每个条带宽度为8个磁盘的8个逻辑卷,这样在新


增加磁盘时可以也增加为8个磁盘的新卷。但必须要保证8个磁盘的条带宽度能够支持系统的每秒IO吞吐量。


如果你的条带宽度设置得比较小,就需要估算出你的各个数据库文件的IO负载,并根据负载量不同将他们分别


部署到不同卷上一分担IO负载。


2.2.2 人工条带
如果系统不支持LVM或者硬件条带,IO负载就必须由DBA根据数据库文件的IO负载不同手工将他们分散到各个磁


盘上去以保证整个系统的IO负载均衡。


有许多DBA会将哪些使用频率非常高的表和它的索引分开存储。但实际上这种做法并不正确。在一个事务中,索


引会先被读取到然后再读取表,它们的IO操作是有前后顺序的,因此索引和表存储在同一个磁盘上是没有冲突


的。仅仅因为一个数据文件即包含了索引又包含了数据表而将它分割是不可取的。我们需要根据文件上的IO负


载是否已经影响到了数据库的性能来决定是否将数据文件分割。


为了正确分布文件,我们首先必须先了解各个数据库文件的IO负载需求以及IO系统的处理能力。鉴定出每个文


件的IO吞吐量。找出哪些文件的IO吞吐率最高而哪些IO量很少,将它们分散分布到所有磁盘上去以平衡IO吞吐


率。


如果你不了解或者无法预计文件的IO负载,就只能先估计他们的IO负载来规划文件分布,在系统运行过程中再


做调整。


2.2.3 文件分离
无论是采用操作系统条带化还是手工IO分布方式,如果IO系统或者IO规划布置无法满足IO吞吐率的要求,我们


就需要考虑将高IO吞吐率的文件和其他文件分离。我们可以在存储规划阶段或者系统运行阶段找出那样的文件





除了IO吞吐率,在决定是否分割文件时,我们还需要考虑可恢复性以及数据容量扩张问题。


但是在分割文件之前,一定要确认存在IO瓶颈,然后再根据产生IO瓶颈的数据定位到存在高IO吞吐率的文件(


热点文件)。


2.2.3.1  表、索引和临时表空间
如果具有高IO吞吐率的数据文件属于包含表和索引的表空间,我们就需要找出这些文件的IO是否可以通过SQL语


句调优或者优化应用程序来降低。


如果具有高IO吞吐率的数据文件属于临时表空间,那我们就需要检查是否可以通过避免或调优SQL语句的排序操


作来降低IO。


经过应用调优后,如果IO分布仍然无法满足IO吞吐的要求,我们就需要考虑分离高IO吞吐率的数据文件了。


2.2.3.2  Redo Log文件
如果具有高IO吞吐率的文件是Redo Log文件,则需要考虑将Redo Log文件与其他文件分离,可以通过以下配置


来实现:


将所有Redo Log文件放到没有任何其他文件的磁盘上去。考虑到可恢复性,需要将一个Redo Log组中的成员文


件分别放到不同的物理磁盘上去;
将每个Redo Log组放到一个没有任何其他文件的单独磁盘上;
通过操作系统条带化工具,将Redo Log文件条带化分布到多个磁盘上去;
不要将Redo Log文件放到RAID 5上去
Redo Log文件是由LGWR进程序列化的写入的。如果在同一个磁盘上不存在并发的其他IO操作,写入效率就更高


。我们需要确认已经没有其他优化调整空间再考虑分割Redo Log文件。如果系统支持AIO但还没有激活该特性,


可以考虑激活AIO看是否能解决Redo Log的IO性能瓶颈。


2.2.3.3  归档Redo Log
如果归档变慢,我们也许可以通过使LGWR的写操作与Archive进程的读操作分离来避免LGWR进程鱼Archive进程


直接的IO冲突。我们可以同交替成组存放Redo Log文件来实现。


例如,我们有四组Redo Log,每组包含两个Log文件:(A1,A2)、(B1,B2)、(C1,C2)、(D1,D2)。我


们就可以以下面这种存放方式将它们分布存储到四个磁盘上去来实现磁盘分离访问:(A1,C1)、(A2、C2)


、(B1,D1)、(B2,D2)。


当LGWR进程做日志切换时,如从A组切换到B组,LGWR开始向B组写Redo Log(第三、四块磁盘),而Archive进


程则从B组读取数据(第一、二块磁盘)写入归档文件中去,他们分别访问的是不同磁盘,因而避免了IO冲突。


2.3     三种简单的配置方法
这里给出三种简单的操作系统IO配置的例子,包括如何简单地计算来决定磁盘的拓扑结构、条带深度等等。


2.3.1 将所有文件条带化到所有磁盘上去
IO配置最简单的方法就是建立一个大的逻辑卷,将所有磁盘都条带化到这个卷中去。考虑到可恢复性,这个卷


需要被镜像(RAID 1)。每个磁盘的条带深度必须大于频繁执行的IO操作的最大IO大小。这种配置对大多数情


况都能提供足够的性能支持。


2.3.2 将归档日志放到另外的磁盘上去
在归档模式下,如果归档文件也和其他文件放在同一个条带化的卷中,那么当归档进程对Redo Log进行归档时


,会大大增加磁盘的IO负载。将归档日志转移到其他磁盘上有如下好处:


归档进程效率提高;
当归档时,其他进程受到归档进程的影响
归档日志的磁盘数由归档日志产生的频率以及归档存储容量决定。


2.3.3 将Redo Log文件放到另外的磁盘上去
在更新非常频繁的OLTP系统中,Redo Log的写操作非常频繁。将Redo Log文件转移到其他磁盘上可以有如下好


处:


写Redo Log的读写效率最高,因而事务的执行也能获得最佳性能;
写Redo Log操作不会影响任何其他IO操作
Redo Log的磁盘数量有Redo Log的大小决定。由于现在的磁盘容量都非常大,通常配置两个磁盘(如果做镜像


则需要四块)就足够了。并且,根据我们前面的分析,将Redo Log文件交互的存放到两块磁盘上去能避免LGWR


进程的写操作与ARCH进程的读操作之间的IO冲突。


3 Oracle中的IO问题及其解决思路
对于负载偏重点不同,我们可以简单的将数据库系统分为CPU负载系统(CPU Bound System)和IO负载系统(IO 


Bound System)。顾名思义,CPU负载系统的资源瓶颈在于CPU,而IO负载系统的瓶颈在于磁盘IO。


我们可以通过操作系统的一些命令来确认一个系统是否是存在IO负载。在UNIX下,可以使用"iostat"或者"sar 


-d"来看系统的IO情况;在windows下,可以通过系统的性能监视器查看,但由于性能监控器中看到的IO是静态


的IO总量信息,并不直观,因此也可以用本站的TopShow工具来查看实时的IO信息。


在UNIX系统下,发现CPU IDEL很低并不一定代表这是一个CPU负载系统。一个IO负载系统在表面上看CPU的IDEL


值也可能很低:


oracle@db01:/export/home/oracle> sar -u 1 10
 
HP-UX hkhpdv45 B.11.23 U ia64    10/24/07
 
09:43:05    %usr    %sys    %wio   %idle
09:43:06 43 25 30  1
09:43:07 44 36 19 1
09:43:08 23 27 44  6
09:43:09 12 37 50 1
09:43:10 1036 51 3
09:43:11 15 34 42  9
09:43:12 18 36 44 3
09:43:13 17 35 46 2
09:43:14 12 32 52 4
09:43:15 12 31 56 1
  
Average  21 33 43  3
我们可以注意到,实际上WIO是引起CPU IDEL过低的主要原因。WIO是当一个进程需要运行或已经运行后,因为


需要等待IO事件而被阻塞了。事实上CPU是处于IDEL状态(在某些系统中,已经将WIO取消并归为IDEL),真正


的原因是系统中存在IO瓶颈。


通过iostat或者sar -d我们可以找出存在IO瓶颈的磁盘设备,如果该磁盘设备是用于Oracle 数据库存储文件的


,我们可以判断出是数据库存在IO问题。在windows下,可以通过TopShow来找出哪个进程正在进行大量IO传输


,如果是Oracle进程,也可以判断为是数据库存在IO问题。


确认系统存在IO问题后,我们就需要定位到底是什么引起的IO问题,该采取什么措施来解决问题。根据我们前


面的介绍,Oracle中存在各种IO,要定位IO,最好的工具是statspack(在10g以后,可以用AWR)。通过


statspack report的Top 5 Events,我们可以看到对系统系能影响最大的5个等待event,而不同的IO问题会对


应不同Event,所以,我们可以根据这些event采取不同的措施来解决IO问题。下面是一个典型的IO负载系统的


Top 5 Event:


Top 5 Timed Events
~~~~~~~~~~~~~~~~~~     % Total
Event  Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
db file sequential read   70,575,969     344,200    53.34
db file scattered read 11,240,748     163,242    25.30
log file sync     657,241 36,363     5.64
CPU time  35,290     5.47
log file parallel write   833,799 20,767     3.22
可以看到,前两个时间“db file sequential read”和“db file scattered read”分别占了总等待时间的


53.34%和25.30%,而我们前面提到这两个事件分别是由索引扫面和全表扫面(或快速索引扫面)引起的,因此


,能解决索引扫面问题和全表扫面问题就能解决这个系统的IO瓶颈。


IO问题到底对CPU有多大影响呢?我们用以上例子中的数据分析一下。从等待时间统计数据中,我们看到的是时


间在总等待时间中所占的比例。而系统的“总响应时间 ”= “等待时间 ”+ “CPU工作时间”(注意,上面


Top 5事件中的“CPU Time”不是指CPU的工作时间,而是指CPU的等待时间)。“CPU工作时间”的数据我们可


以在“Instance Activities Stats for DB”这一分类统计数据中找到:


Statistic Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
CPU used by this session  17,136,868  396.7 15.5
先计算出“总等待时间” = 344,200 * 100% / 53.34% = 645,294s


“总响应时间” = “总等待时间” + “CPU工作时间” = 645,294 + 17,136,868 = 17,782,162s


我们可以算出“CPU工作时间”、“db file sequential read”和“db file scattered read”分别在“总响


应时间中所占的比例为:


CPU工作时间 = 17,136,868 / 17,782,162 = 96.4%


“db file sequential read” = 344,200 / 17,782,162 = 1.9%


“db file scattered read” = 163,242 / 17,782,162 = 0.9%


可见,IO事件所引起的等待时间在总响应时间所占比例并不大。因此,我们在做系统优化之前先分析系统是CPU


负载系统还是IO负载系统对于我们的优化方向和最终的优化效果起很大的作用。


以下事件是可能由IO问题引起的等待事件,在IO负载系统中,我们要特别关注这些事件:


与数据文件相关的IO事件
'db file sequential read'    


'db file scattered read'


'db file parallel read'


'direct path read'   


'direct path write'  


'direct path read (lob)'


'direct path write (lob)'


与控制文件相关的IO事件
'control file parallel write'


'control file sequential read'


'control file single write'


与Redo日志相关的IO事件
'log file parallel write'    


'log file sync' 


'log file sequential read'


'log file single write'


'switch logfile command'


'log file switch completion'


'log file switch (clearing log file)'


'log file switch (checkpoint incomplete)'


'log switch/archive'


'log file switch (archiving needed)'


与Buffer Cache相关的IO事件
'db file parallel write'


'db file single write'


'write complete waits'


'free buffer waits'


下面我们就分别介绍如何解决IO问题。


3.1     IO调优的思路及常用手段
通过对statspack或者awr报告的分析,我们可以得知是那些IO相关事件引起的IO问题。针对不同的事件,可以


采取不同的分析、处理方法。而有一些通用的方法并不是针对特定的事件的。我们这里先介绍一下这些方法。


3.1.1 通过SQL调优来减少IO请求
一个没有任何用户SQL的数据库几乎不产生任何IO。基本上数据库所有的IO都是直接或间接由用户提交的SQL所


导致的。这意味着我们可以通过控制单个SQL产生的IO来降低数据库总的IO请求。而通过SQL调优来降低SQL查询


计划中的IO操作次数则是降低SQL产生IO的最好方法。数据库的性能问题通常是由少数几个SQL语句所导致的,


它们产生了大量IO导致了整个数据库的性能下降。优化几条问题语句往往就能解决整个数据库的IO性能问题。


从Oracle 10g开始,ADDM能够自动检测出问题语句,同时,再通过查询优化建议器能够自动优化语句并降低它


们对IO的消耗。关于ADDM和查询优化建议器可以参考文章《Oracle 10G 新特性——ADDM和查询优化建议器》。


3.1.2 通过调整实例参数来减少IO请求
在这种方法中,主要有两种途径来实现对IO的优化。


使用内存缓存来减少IO
通过一些内存缓存,如Buffer Cache、Log Buffer、Sort Area,可以降低数据库对IO的请求。


当Buffer Cache被增大到一定大小时,绝大多数结果可以直接从缓存中获取到,而无需从磁盘上读取了。而在


进行排序操作时,如果Sort Area足够大,排序过程中产生的临时数据可以直接放在内存中,而无需占用临时表


空间了。


调整multiblock IO(多数据块IO)的大小
控制Multiblock IO的参数叫DB_FILE_MULTIBLOCK_READ_COUNT,它控制在多数据块读时一次读入数据块的次数


。适当增加这个参数大小,能够提高多数据块操作(如全表扫描)的IO效率。例如,读取100M数据,如果每次


读取1M一共读取100次的效率就比每次读取100K一共读取1000次更快。但是这个数字达到一定大小后,再增加就


作用不大了:每次10M一共读100次来读取1G的数据的效率和单独一次读取1G数据的效率是没有多大区别的。这


是因为IO效率受到2个因素的影响:IO建立时间和IO传输时间。


IO建立时间对于不同IO大小来说都是相同的,它决定了对小IO的总的IO时间,增大Multiblock IO大小可以减少


IO建立时间;


IO传输时间与IO大小是成正比的,在小IO时,IO传输时间一般比IO建立时间少,但对于大IO操作来说,IO传输


时间决定了总的IO时间。因此Multiblock IO大小增大到一定大小时,它对总的IO时间影响就不大了。


3.1.3 在操作系统层面优化IO
如我们前面所介绍的,利用一些操作系统提供的提升IO性能的特性,如文件系统的异步IO、Direct IO等来优化


数据库系统的IO性能。另外一种方法就是增加每次传输的最大IO大小的限制(大多数Unix系统中,由参数


max_io_size控制)。


3.1.4 通过Oracle ASM实现对IO的负载均衡
ASM(Automatic Storage Manager自动存储管理)是从Oracle 10g开始引入的。它是一个建立在数据库内核中


的文件系统和卷管理器。它能自动将IO负载均衡到所有可用的磁盘启动器上去,一避免“热区”。ASM能防止碎


片,因此无需重建数据来回收空间。数据被均衡分布到所有硬盘上。


3.1.5 通过条带化、RAID、SAN或者NAS实现对IO的负载均衡
这个方法通过一些成熟的存储技术,如条带化、RAID、SAN和NAS,来将数据库IO分布到多个可用的物理磁盘实


现负载均衡,以避免在还存在空闲可用磁盘时出现的磁盘争用和IO瓶颈问题。


关于这几种存储技术,我们文章的前面部分都有做介绍。


3.1.6 通过手工布置数据库文件到不同的文件系统、控制器和物理设备上来重新分布数据库IO
当数据库系统中缺乏以上各种存储技术手段时,我们可以考虑使用这种方式。这样做的目的是使数据库的IO得


到均匀分布,从而避免在还有空闲磁盘时出现磁盘争用和IO瓶颈问题。当然这种手工分布IO方法是无法达到以


上的自动分布IO的效果的。


3.1.7 其他手段
系统中总会存在一些IO是无法消除或降低的。如果采用以上手段还不能满足IO性能要求的话,可以考虑这两种


方法:


将老数据移除你的生产数据库(Housekeep)
采用更多、更快的硬件
3.2     数据文件相关的IO事件
数据库系统中的大多数的IO请求都是针对数据文件的。因此大多数情况下,与数据文件相关的IO事件是引起系


统IO性能的主要原因。这些事件也是我们文章需要重点介绍的事件。下面分别针对不同事件介绍问题的解决思


路。


3.2.1 db file sequential read
这个事件是是最常见的IO等待事件。它一般发生在读取单独数据块时,如读取索引数据块或者通过索引访问一


个表数据块,另外在读取数据文件头数据块时也会发生db file sequential read等待事件。


当发现这个等待事件成为系统等待事件中的主要事件,我们可以通过一下方法来处理:


3.2.1.1  优化Top SQL
从statspack或者awr报告中的“SQL ordered by Reads”部分或者通过V$SQL视图找出系统中的Top SQL,对SQL


进行调优以减少IO请求。


当SQL中存在Index Range Scan时,如果访问的索引的选择性不好就会导致需要访问过多的数据块,这时可以通


过建立一个、或强制SQL使用一个已经存在的选择性更好的索引。这样使我们访问更少的数据块来获取到需要的


数据。
 


SQL> select object_id, object_name
  2  from t_test1
  3  where owner = 'SYS'
  4  and created > sysdate - 30;
 
no rows selected
 
Execution Plan
----------------------------------------------------------
Plan hash value: 4014220762
 
--------------------------------------------------------------------------------
| Id  | Operation   | Name | Rows  | Bytes | Cost (%CPU)
| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    | |     1 |    39 |    11   (0)
| 00:00:01 |
|*  1 |  TABLE ACCESS BY INDEX ROWID| T_TEST1 |     1 |    39 |    11   (0)
| 00:00:01 |
|*  2 |   INDEX RANGE SCAN  | T_TEST1_IDX1 |   576 |  |     1   (0)
| 00:00:01 |
--------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("OWNER"='SYS' AND "CREATED">SYSDATE@!-30)
 
 
Statistics
----------------------------------------------------------
  0  recursive calls
  0  db block gets
   658  consistent gets
 45  physical reads
  0  redo size
   339  bytes sent via SQL*Net to client
   374  bytes received via SQL*Net from client
  1  SQL*Net roundtrips to/from client
  0  sorts (memory)
  0  sorts (disk)
  0  rows processed
 
SQL> create index t_test1_idx2 on t_test1(owner, created);
 
Index created.
 
SQL> select object_id, object_name
  2  from t_test1
  3  where owner = 'SYS'
  4  and created > sysdate - 30;
 
no rows selected
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 3417015015
---------------------------------------------------------------------------------
| Id  | Operation   | Name | Rows  | Bytes | Cost (%CPU)
| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    ||    49 |  1911 |     2   (0)
| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T_TEST1 |    49 |  1911 |     2   (0)
| 00:00:01 |
|*  2 |   INDEX RANGE SCAN  | T_TEST1_IDX2 |    49 |  |     1   (0)
| 00:00:01 |
---------------------------------------------------------------------------------
 
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("OWNER"='SYS' AND "CREATED">SYSDATE@!-30)
 
 
Statistics
----------------------------------------------------------
  1  recursive calls
  0  db block gets
  2  consistent gets
  1  physical reads
  0  redo size
   339  bytes sent via SQL*Net to client
   374  bytes received via SQL*Net from client
  1  SQL*Net roundtrips to/from client
  0  sorts (memory)
  0  sorts (disk)
  0  rows processed
 


如果索引存在碎片,那每个索引数据块上的索引数据就更少,会导致我们需要访问更多的索引数据块。这时,


我们需要考虑重建索引来释放碎片;
判断一个所以是否需要重建,我们介绍一个简单的方法:对一个索引进行结构分析后,如果该索引占用超过了


一个数据块,且满足以下条件之一:B-tree树的高度大于3;使用百分比低于75%;数据删除率大于15%,就需要


考虑对索引重建:


SQL> analyze index t_test1_idx1 compute statistics;
 
Index analyzed.
 
SQL> analyze index t_test1_idx1 validate structure;
 
Index analyzed.
 
SQL> select btree_space, -- if > 8192(块的大小)
  2 height, -- if > 3
  3 pct_used, -- if < 75
  4 del_lf_rows/(decode(lf_rows,0,1,lf_rows)) *100 as deleted_pct -- if > 20%
  5  from index_stats;
 
BTREE_SPACE     HEIGHT   PCT_USED DELETED_PCT
----------- ---------- ---------- -----------
     880032  2 89   0
如果使用的索引的聚簇因子(Clustering Factor)很大,说明一条索引记录指向多个数据块,在返回结果时需


要读取更多的数据块。通过重建表可以降低聚簇因子,因而可以在查找索引时减少表数据块的访问块数。


聚簇因子说明了表数据的物理存储位置相对于一个索引的排序性的符合程度。例如,一个非唯一索引是建立在A


字段上的,如果表数据的存储是以A字段的顺序存储的,则索引与数据的关系如下图:






此时,索引的聚簇因子很低,从图上看到,假如我们需要获取A=A2的数据,只需要读取一个数据块就可以了;


相反,如果表数据物理存储顺序和索引顺序相差很大,就会出现下面的情况:






这时该索引的聚簇因子就很大,可以看到,如果需要获取A=A2的数据,我们需要读取4块或更多的数据块。


 


对索引进行分析后,我们可以从视图DBA_INDEXES中获取到索引的聚簇因子,字段名为Clustoring_Factor。如


果一个索引是一张表主要被使用的索引(或者是该表的唯一索引),且它的聚簇因子过高导致IO请求过高的话


,我们可以考虑采取以下措施来降低IO:


1) 以索引字段的顺序重建表以降低聚簇因子,可以用以下语句重建表(当然,你还需要重建触发器、索引等对


象,还可能需要重建、重新编译有关联对象):


CREATE new_table AS SELECT * FROM old_table ORDER BY A;
2) 建立基于索引字段IOT(索引表)。


如果该索引不是表的主要索引,只是被少量语句引用到,按照以上方式处理的话反而可能会使其他使用更加频


繁的索引的聚簇因子增大,导致系统性能更差。这时我们可以建立包含返回字段的索引,以避免“TABLE 


ACCESS BY INDEX ROWID”。如以下例子:


SQL> set autot trace
SQL> select status from t_test1
  2  where owner = 'DEMO';
 
576 rows selected.
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 4014220762
 
--------------------------------------------------------------------------------
| Id  | Operation   | Name | Rows  | Bytes | Cost (%CPU)
| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    | |   576 |  6336 |    11   (0)
| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T_TEST1 |   576 |  6336 |    11   (0)
| 00:00:01 |
|*  2 |   INDEX RANGE SCAN  | T_TEST1_IDX1 |   576 |  |     1   (0)
| 00:00:01 |
--------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("OWNER"='DEMO')
 
 
Statistics
----------------------------------------------------------
   465  recursive calls
  0  db block gets
   222  consistent gets
 43  physical reads
  0  redo size
  8368  bytes sent via SQL*Net to client
   803  bytes received via SQL*Net from client
 40  SQL*Net roundtrips to/from client
  8  sorts (memory)
 0  sorts (disk)
   576  rows processed
 
SQL> create index t_test1_idx3 on t_test1(owner, status) compute statistics;
 
Index created.
 
SQL> select status from t_test1
  2  where owner = 'DEMO';
 
576 rows selected.
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2736516725
 
--------------------------------------------------------------------------------
| Id  | Operation   | Name | Rows  | Bytes | Cost (%CPU)| Time|
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT | |   576 |  6336 |     2   (0)| 00:00:01|
|*  1 |  INDEX RANGE SCAN| T_TEST1_IDX3 |   576 |  6336 |     2   (0)| 00:00:01|
--------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("OWNER"='DEMO')
 
 
Statistics
----------------------------------------------------------
 1  recursive calls
  0  db block gets
 43  consistent gets
  3  physical reads
  0  redo size
  8152  bytes sent via SQL*Net to client
   803  bytes received via SQL*Net from client
 40  SQL*Net roundtrips to/from client
  0  sorts (memory)
  0  sorts (disk)
   576  rows processed
 


通过分区裁剪(partition pruning)技术来减少的SQL对数据块的访问。
采用分区裁剪技术,Oracle优化器会先分析FROM和WHERE字句,在建立访问分区列表时将那些不会被访问到的分


区排除。例如,我们的表T_TEST1的owner字段的值有“SYS、SYSTEM、XDB、DEMO、TEST”,如果我们按照owner


字段建立的是分区表:


CREATE TABLE t_test1
(object_id    NUMBER(5), 
 object_name  VARCHAR2(30),
 owner  VARCHAR2(20),
 created DATE)
PARTITION BY LIST(owner)
(
PARTITION owner_sys VALUES('SYS', 'SYSTEM'),
PARTITION owner_xdb VALUES ('XDB'),
PARTITION owner_demo VALUES('DEMO'),
PARTITION owner_test VALUES('TEST'),
PARTITION owner_others VALUES(DEFAULT)
);
则对于以下语句:


select object_name
from t_test1
where owner in ('DEMO', 'TEST')
and created > sysdate - 30;
优化器会先将分区owner_sys、owner_xdb、owner_others从分区访问列表中裁剪出去,只访问分区owner_demo


和owner_test上的数据或者通过这两个分区上的索引来访问数据。


3.2.1.2  处理非SQL导致的IO问题
如果从statspack或者AWR报告中找不到明显产生db file sequential read事件的SQL,则该等待事件可能是由


于以下原因导致的:


热点数据文件或磁盘
数据文件所在的磁盘IO负荷过重导致对IO请求反映慢,这时,我们可以通过statspack或AWR报告中的“File 


I/O Statistics”部分(或者通过V$FILESTAT视图)来找到热点磁盘:


Statspack report:


Tablespace  Filename
------------------------ ----------------------------------------------------
 Av Av     Av    Av   Buffer Av Buf
 Reads Reads/s Rd(ms) Blks/Rd  Writes Writes/s Waits Wt(ms)
-------------- ------- ------ ------- ------------ -------- ---------- ------
AFW_DATA /export/home/icssprd/data/data17/icssprd_afw_data_01
   726  0    4.3     1.0  381   0  0
 
AFW_INDX /export/home/icssprd/data/data18/icssprd_afw_indx_01
 1,741  0    6.3     1.0   2,104   0  0
 
CSS_AN_DATA /export/home/icssprd/data/data03/icssprd_css_an_data
  200,649  5    1.8     3.2  24,192   1  0
 /export/home/icssprd/data/data04/icssprd_css_an_data
  242,462  6    1.6     3.1  26,985   1  3    6.7
 
CSS_AN_INDX /export/home/icssprd/data/data13/icssprd_css_an_indx
   70,789  2    5.0     1.6   5,330   0  0
 
CSS_AUDIT_RESOURCES_DATA /export/home/icssprd/data/data10/icssprd_css_audit_r
 2,394  0    0.6     1.0   1,781   0 0
 
CSS_AUDIT_RESOURCES_INDX /export/home/icssprd/data/data11/icssprd_css_audit_r
   248  0    4.3     1.0   52   0  0
 
... ...
视图:


SQL> select b.name, phyrds, phywrts
  2  from V$FILESTAT a, V$DATAFILE b
  3  where a.file# = b.file#;
 
NAME
--------------------------------------------------------------------------------
    PHYRDS    PHYWRTS
---------- ----------
C:/ORACLE/PRODUCT/10.2.0/ORADATA/EDGAR/DATAFILE/O1_MF_SYSTEM_20TFOB4Q_.DBF
    132767 11565
 
C:/ORACLE/PRODUCT/10.2.0/ORADATA/EDGAR/DATAFILE/O1_MF_UNDOTBS1_20TFQP78_.DBF
 1943 19924
 
C:/ORACLE/PRODUCT/10.2.0/ORADATA/EDGAR/DATAFILE/O1_MF_SYSAUX_20TFSGC6_.DBF
659458     100811
 
... ...
找到热点数据文件(磁盘)后,我们可以考虑将数据文件转移到性能更高的存储设备上去,或者利用我们上述


说的条带化、RAID等存储技术来均衡IO负荷。


热点数据段
从Oracle9.2开始,出现了数据段的概念。每个表和索引都存储在自己的数据段中。我们可以通过视图V


$SEGMENT_STATISTICS查找物理读最多的段来找到热点数据段。通过对热点段的分析,考虑采用重建索引、分区


表等方式来降低该数据段上的IO负荷。


SQL> select owner, object_name, tablespace_name, object_type, value
  2  from V$SEGMENT_STATISTICS
  3  where statistic_name = 'physical reads'
  4  order by value desc;
 
OWNER  OBJECT_NAME
------------------------------ ------------------------------
TABLESPACE_NAME  OBJECT_TYPE     VALUE
------------------------------ ------------------ ----------
SYS    CONTEXT$
SYSTEM TABLE 71
 
SYS    I_CONTEXT
SYSTEMINDEX 70
 
... ...
另外,我们还可以根据视图v$session_wait中的P1(热点段所在的数据文件号)、P2(发生db file 


sequential read事件的起始数据块)、P3(数据块的数量,db file sequential read读取数据块数量为1)来


定位出热点段:


先找出文件号、起始数据块、数据块数量:


SQL> select p1 "fileid", p2 "block_id", p3 "block_num"
  2  from v$session_wait
  3  where event = 'db file sequential read';
 
    fileid   block_id  block_num
---------- ---------- ----------
  396 44869  1
然后根据找出的文件号、起始数据块、数据块数量来定位出数据段:


SQL> select
  2     segment_name     "Segment Name",
  3     segment_type     "Segment Type",
  4     block_id "First Block of Segment",
  5     block_id+blocks  "Last Block of Segment"
  6  from dba_extents
  7  where &fileid = file_id
  8  and &block_id >= block_id
  9  and &block_id <= block_id+blocks;
Enter value for fileid: 396
old   7: where &fileid = file_id
new   7: where 396 = file_id
Enter value for block_id: 44869
old   8: and &block_id >= block_id
new   8: and 44869 >= block_id
Enter value for block_id: 44869
old   9: and &block_id <= block_id+blocks
new   9: and 44869 <= block_id+blocks
 
Segment Name
--------------------------------------------------------------------------------
Segment Type  First Block of Segment Last Block of Segment
------------------ ---------------------- ---------------------
CSS_TP_SHMT_QUEUE_ACTIVITY
TABLE  44841 44873
3.2.1.3  调整Buffer Cache
如果系统中即不存在性能有问题的SQL语句,而且所有磁盘的IO负载也比较均衡(不存在热地磁盘),则我们需


要考虑增加Buffer Cache来降低磁盘IO请求。


在8i,主要是根据缓存命中率(Buffer Cache Hit Ratio)来调整buffer cache。当Buffer Cache调整到一定


大小,对命中率没什么影响了时,就没有必要在增大Buffer Cache了。可以通过以下语句来查看Buffer Cache


命中率:


SQL> select 1-(physical_reads)/(consistent_gets+db_block_gets)
  2  from v$buffer_pool_statistics;
 
1-(PHYSICAL_READS)/(CONSISTENT_GETS+DB_BLOCK_GETS)
--------------------------------------------------
 .95628981
在9i中,可以利用statspack report中的Buffer Cache建议部分来调整Buffer Cache的大小。


Buffer Pool Advisory for DB: ICSSPRD  Instance: icssprd  End Snap: 259
-> Only rows with estimated physical reads >0 are displayed
-> ordered by Block Size, Buffers For Estimate
 
   Size for  Size Buffers for  Est Physical  Estimated
P   Estimate (M) Factr Estimate   Read Factor     Physical Reads
--- ------------ ----- ---------------- ------------- ------------------
D    304    .1   37,715  9.18 5,928,235,496
D    608    .2   75,430  6.88 4,443,709,043
D    912    .3  113,145  5.73 3,699,496,220
D  1,216    .4  150,860  3.87 2,502,670,372
D  1,520    .5  188,575  2.32 1,499,049,228
D  1,824    .6  226,290  1.70 1,099,326,418
D  2,128    .7  264,005  1.41   912,042,579
D  2,432    .8  301,720  1.22   790,925,174
D  2,736    .9  339,435  1.09   703,357,378
D  2,992   1.0  371,195  1.00   645,905,997
D  3,040   1.0  377,150  0.99   636,992,420
D  3,344   1.1  414,865  0.90   583,996,250
D  3,648   1.2  452,580  0.84   542,063,246
D  3,952   1.3  490,295  0.79   508,261,496
D  4,256   1.4  528,010  0.74   480,472,150
D  4,560   1.5  565,725  0.71   455,533,563
D  4,864   1.6  603,440  0.67   434,743,759
D  5,168   1.7  641,155  0.64   416,285,837
D  5,472   1.8  678,870  0.62   400,208,242
D  5,776   1.9  716,585  0.60   385,785,401
D  6,080   2.0  754,300  0.57   365,597,932
  -------------------------------------------------------------
这里,Est Physical Read Factor是估算的从磁盘物理读取次数与从buffer cache中读取的次数的比值。从意


见估算的图表中,当Buffer Cache的增长对该因子影响不大时,则说明无需在增大Buffer Cache,我们就可以


去相应临界点的大小作为Buffer Cache的大小。上述例子中,我们可以考虑设置Buffer Cache大小为2992M。


在Oracle10g中,引入了新的内存管理特性——自动共享内存管理(Automatic Shared Memory Management 


ASMM)。基于这一特性,oracle能够自动根据当前的负荷计算出最优的Buffer Cache大小。关于ASMM,可以参


见文章《Oracle内存全面分析》的SGA_TARGET部分。


我们可以采用多尺寸缓冲池技术将热点数据段(表或索引)KEEP在缓冲池中:


SQL> alter table t_test1 storage(buffer_pool keep);
 
Table altered.
关于多尺寸缓冲的更多内容,可以参考文章《Oracle内存全面分析》的“多缓冲池部分”部分。


3.2.1.4  Housekeep历史数据
对于一些被频繁访问到的大表,我们需要定期对其做housekeep,将一些不用的、老的数据从表中移除,以减少


访问的数据块。定期对含有时间轴的Transaction表做housekeep是降低IO负载的重要措施。


3.2.2 db file scattered read
这是另外一个常见的引起数据库IO性能问题的等待事件。它通常发生在Oracle将“多数据块”读取到Buffer 


Cache中的非连续(分散的 Scattered)区域。多数据块读就是我们上述所说的一次读


取“DB_FILE_MULTIBLOCK_READ_COUNT”块数据块,前面提到,它通常发生在全表扫描(Full Table Scan)和


快速全索引扫描(Fast Full Index Scan)时。当发现db file scattered read等待事件是系统引起IO性能的


主要原因时,我们可以采取以下措施对系统进行优化。


3.2.2.1  优化存在Full Table Scan和Fast Full Index Scan的SQL语句
我们可以首先从statspack或者awr报告中的“SQL ordered by Reads”部分中找出存在Full Table Scan和Fast 


Full Index Scan的Top SQL。因为这些Top SQL往往是整个系统的瓶颈。


从9i开始,我们还可以通过视图V$SQL_PLAN来查找系统中存在Full Table Scan和Fast Full Index Scan的SQL


语句。查找Full Table Scan的语句:


select sql_text from v$sqlarea t, v$sql_plan p
 where t.hash_value=p.hash_value and p.operation='TABLE ACCESS'
   and p.options='FULL';
查找Fast Full Index Scan的语句


select sql_text from v$sqlarea t, v$sql_plan p
 where t.hash_value=p.hash_value and p.operation='INDEX'
   and p.options='FULL SCAN';
Full Table Scan通常是由于以下几个原因引起的:


条件字段上没有索引;
在这种情况下,如果表的数据量比较大,我们就需要在相应字段上建立起索引。


CBO中,对象的统计数据不正确
CBO中,如果对象的统计数据或者其柱状图(Histogram)信息不正确,会导致优化器计算出错误的查询计划,


从而选择全表扫描。这种情况下,我们要做的就重新分析(Analyze)表、索引及字段。


CBO中,SQL语句中引用到了无法估算统计数据的对象
在PLSQL中,可以建立一些高级的数据类型,如“TABLE OF”、ARRAY等,通过TABLE、CAST函数可以在SQL语句


中将这些对象当成表来处理。而这些对象的数据只存在于调用PLSQL的会话中,因此他们没有相应的统计数据,


Oracle会为他们生产一些假的统计数据以完成查询计划代价估算。但是基于这些假的数据计算出的查询计划一


般是错误的。我们可以考虑通过提示来强制SQL使用索引或者强制SQL采用RBO优化器。


此外,如果SQL中引用到了临时表(Temporary Table)也会产生同样的问题。其原因和解决方法和上面相同。


优化器认为索引扫描代价过高;
在Oracle中存在一个参数optimizer_index_cost_adj,该参数的值代表一个百分数,如果对索引扫描的代价达


到或超过全表扫描的代价的这个百分比值时,优化器就采用全表扫描。


optimizer_index_cost_adj是一个全局性的参数,它的合理值是通过长期调整出来的。一般来说是一个介于1到


100之间的数字。我们可以按照以下方法来选取optimizer_index_cost_adj的合理值。


先由以下语句得出optimizer_index_cost_adj的一个初始值:


SQL> select
  2     a.average_wait  "Average Waits FTS"
  3     ,b.average_wait  "Average Waits Index Read"
  4     ,a.total_waits /(a.total_waits + b.total_waits)  "Percent of FTS"
  5     ,b.total_waits /(a.total_waits + b.total_waits)  "Percent of Index Scans"
  6     ,(b.average_wait / a.average_wait)*100   "optimizer_index_cost_adj"
  7  from
  8     v$system_event  a,
  9     v$system_event  b
 10  where a.EVENT = 'db file sequential read'
 11    and b.EVENT = 'db file scattered read';
 
Average Waits FTS Average Waits Index Read Percent of FTS Percent of Index Scans
----------------- ------------------------ -------------- ----------------------
optimizer_index_cost_adj
------------------------
     1.25     1.06     .041867874     .958132126
   84.8
这里,84.8是我们系统的初始值。在系统经过一段时间运行后,再次运行上面的语句,重新调整


optimizer_index_cost_adj的值。经过多次如此反复的调整之后,最终上面语句得出值趋于稳定,这时这个值


就是符合我们系统性能需求的最合理的值。


当然这个数值也可以通过statspack的历史数据来调整,在9i中:


select to_char(c.end_interval_time, 'MM/DD/YYYY') "Date",






  sum(a.time_waited_micro)/sum(a.total_waits)/10000 "Average Waits FTS",






  sum(b.time_waited_micro)/sum(b.total_waits)/10000 "Average Waits Index Read",






  (sum(a.total_waits) / sum(a.total_waits + b.total_waits)) * 100 "Percent of FTS",






  (sum(b.total_waits) / sum(a.total_waits + b.total_waits)) * 100 "Percent of Index Scans",






  (sum(b.time_waited_micro)/sum(b.total_waits)) /






  (sum(a.time_waited_micro)/sum(a.total_waits)) * 100 "optimizer_index_cost_adj"






from dba_hist_system_event a, dba_hist_system_event b, dba_hist_snapshot c






where a.event_name = 'db file scattered read'






and   b.event_name = 'db file sequential read'






and   a.snap_id = c.snap_id






and   b.snap_id = c.snap_id






group by c.end_interval_time






order by 1;
10g中:


select to_char(c.snap_time, 'MM/DD/YYYY') "Date",






  sum(a.time_waited_micro)/sum(a.total_waits)/10000 "Average Waits FTS",






  sum(b.time_waited_micro)/sum(b.total_waits)/10000 "Average Waits Index Read",






  (sum(a.total_waits) / sum(a.total_waits + b.total_waits)) * 100 "Percent of FTS",






  (sum(b.total_waits) / sum(a.total_waits + b.total_waits)) * 100 "Percent of Index Scans", (sum


(b.time_waited_micro)/sum(b.total_waits)) /
 (sum(a.time_waited_micro)/sum(a.total_waits)) * 100 "optimizer_index_cost_adj"


from stats$system_event a, stats$system_event b, stats$snapshot c


where a.event = 'db file scattered read'


and   b.event = 'db file sequential read'


and   a.snap_id = c.snap_id


and   b.snap_id = c.snap_id


group by c.snap_time


order by 1;
当optimizer_index_cost_adj的值对于整个系统来说已经是比较合理的值,而某些语句由于该值选择了全表扫


描扫描导致了IO性能问题时,我们可以考虑通过提示来强制语句命中索引。


建立在条件字段上的索引的选择性不高,结合上一条导致全表扫描;
当索引的选择性不高,且其代价过高,系统则会选择全表扫描来读取数据。这时我们可以考虑通过选择/建立选


择性比较高的索引,使查询命中索引从而避免全表扫描。


SQL> create index t_test1_idx1 on t_test1(owner) compute statistics;
 
Index created.
 
SQL> set autot trace
SQL> select object_name
  2  from t_test1
  3  where owner = 'SYS'
  4  and created > sysdate - 30;
 
no rows selected
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 1883417357
 
-----------------------------------------------------------------------------
| Id  | Operation | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT  | |    49 |  1715 |   152   (2)| 00:00:02 |
|*  1 |  TABLE ACCESS FULL| T_TEST1 |    49 |  1715 |   152   (2)| 00:00:02 |
-----------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("OWNER"='SYS' AND "CREATED">SYSDATE@!-30)
 
... ...
 
SQL> create index t_test1_idx2 on t_test1(owner, created) compute statistics;
 
Index created.
 
SQL> select object_name
  2  from t_test1
  3  where owner = 'SYS'
  4  and created > sysdate - 30;
 
no rows selected
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 3417015015
 
--------------------------------------------------------------------------------
| Id  | Operation   | Name | Rows  | Bytes | Cost (%CPU)
| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    | |    49 |  1715 |     2   (0)
| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T_TEST1 |    49 |  1715 |     2   (0)
| 00:00:01 |
|*  2 |   INDEX RANGE SCAN  | T_TEST1_IDX2 |    49 |  |     1   (0)
| 00:00:01 |
--------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("OWNER"='SYS' AND "CREATED">SYSDATE@!-30)
 
... ...
3.2.2.2  调整DB_FILE_MULTIBLOCK_READ_COUNT
当SQL已经没有优化余地后,问题仍没有解决,我们可以考虑调整DB_FILE_MULTIBLOCK_READ_COUNT大小。其作


用我们在3.1.2中有做叙述,这里不再赘述。不过要注意一点就是,DB_FILE_MULTIBLOCK_READ_COUNT * 


DB_BLOCK_SIZE是一次IO读取的传输量,它不能大于系统的max_io_size大小。


从Oracle 10gR2开始,如果没有设置DB_FILE_MULTIBLOCK_READ_COUNT的大小,Oracle会自动为其调整一个默认


值,这个默认值的大小与平台最大IO大小(max_io_size)相关(对大多数平台来说max_io_size是1M),其大


小被设置为(max_io_size / DB_BLOCK_SIZE)。


3.2.2.3  将频繁访问的全扫描的表CACHE住
由于通过Full Table Scan和Fast Full Index Scan读取的数据块会被放置到Buffer Cache的LRU链表的LRU端,


从而使数据块尽快从Buffer Cache中移出。因此,对于那些会被频繁访问到全扫描的表,且其数据量不大的情


况下,我们可以考虑将它们CACHE住。


SQL> alter table t_test1 cache;
 
Table altered.
对于Fast Full Index Scan的索引对象,则可以考虑把它放置在KEEP池中。


SQL> alter index t_test1_idx1 storage(buffer_pool keep);
 
Index altered.
利用V$SESSION_EVENT视图,我们同样可以找到当前系统中发生全扫描的对象。


SQL> select p1 "fileid", p2 "block_id", p3 "block_num"
  2  from v$session_wait
  3  where event = 'db file scattered read';
 
    fileid   block_id  block_num
---------- ---------- ----------
  359     152972 16
 
SQL> select
  2     segment_name     "Segment Name",
  3     segment_type     "Segment Type",
  4     block_id "First Block of Segment",
  5     block_id+blocks  "Last Block of Segment"
  6  from dba_extents
  7  where &fileid = file_id
  8  and &block_id >= block_id
  9  and &block_id <= block_id+blocks;
Enter value for fileid: 359
old   7: where &fileid = file_id
new   7: where 359 = file_id
Enter value for block_id: 152972
old   8: and &block_id >= block_id
new   8: and 152972 >= block_id
Enter value for block_id: 152972
old   9: and &block_id <= block_id+blocks
new   9: and 152972 <= block_id+blocks
 
Segment Name
--------------------------------------------------------------------------------
Segment Type  First Block of Segment Last Block of Segment
------------------ ---------------------- ---------------------
CSS_TP_SHMT_QUEUE
TABLE 152969   153001
3.2.2.4  利用分区表减少全扫描操作读取的数据块数量
前面我们有介绍分区裁剪(Partition Pruning)技术。将表分区,利用分区裁剪技术,在进行全扫描时只会扫


描在WHERE条件中出现的分区,从而可以减少全扫描所读取到的数据块数量。


3.2.2.5  Housekeep历史数据
同样,housekeep不需要的、历史的数据,减少数据段中的数据块数量,也能减少全扫描的IO请求次数。


3.2.3 db file parallel read
首先,不要被该事件名称所误导——它和并行DML或者并行查询都无关。当从多个数据文件并行读取数据到非联


系的内存(PGA、Buffer Cache)缓冲中时,会发生该等待事件。它通常发生在Recovery操作或者利用缓冲预提


取(Buffer Prefetching)从数据文件并行读取数据时。


我们可以通过以下语句找出发生db file parallel read等待事件的数据文件和数据块:


select p1 "fileid", p2 "block_id", p3 "requests"
  from v$session_wait
 where event = 'db file parallel read';
优化该等待事件的手段可以参考优化db file sequential read等待事件中非SQL优化方法部分。


3.2.4 direct path read & direct path read (lob)
当直接读取(Direct Read)数据到PGA(而不是到Buffer Cache)中去时,会发生Direct Path Read等待事件


。对Lob数据的直接读有一个单独的等待事件——direct path read (lob)。


当Oracle设置支持异步IO时,进程可以在提交IO请求后继续做其他操作,并且在稍后再提取IO请求返回的结果


,在提取结果时就产生了direct path read等待事件。


在没有启用异步IO时,IO请求在完成之前会被阻塞,但在执行IO操作时并不会产生等待事件。进程稍后回来提


取那些已经读取到的IO数据,这时尽管能够很快返回,但仍然会显示direct path read等待事件。


和其他IO等待事件不同的是,对Direct Path Read等待事件要注意以下两点:


等待次数并不等于IO请求次数;
统计(如statspack报告中)得出的Direct Path Read的等待时间并不一定代表该事件引起的真正等待时间。
事件中的P1、P2、P3参数分别代表:


P1:发生等待事件的数据块所在文件号;


P2:发生等待事件的数据块号;


P3:等待事件涉及的连续数据块数量。


直接读(Direct Read)请求一般发生在以下几种情况:


磁盘排序IO(Sort Area不足时,排序用到的临时数据会被写到临时表空间上去,当读取这些数据时就使用直接


读);
并行查询;
预读取(当一个进程认为某个数据块将很快被用到而发出IO请求时)
Hash Join(Hash Area不足)
IO负载系统中,服务进程处理缓存的速度比系统IO返回数据到缓存的速度更快时
通过视图V$SESSION_EVENT我们可以找出当前产生等待的会话,再根据会话中正在进行的操作确定导致等待的原


因。针对不同的原因,我们可以采取不同的措施减少Direct Path Read等待事件。


3.2.4.1  磁盘排序
首先我们可以考虑优化语句以减少排序操作。排序一般是由以下操作引起的:


Order By;
JOIN;
UNION;
Group By;
聚合操作;
Select unique;
Select distinct;
可以尝试在语句中减少没必要的上述操作来避免排序操作。另外,创建索引也会引起排序操作。在专业模式


(Dedicated)下,排序所占用的内存是从PGA中分配出来的一块区域,叫Sort Area,由参数sort_area_size控


制其大小;在MTS中,排序区是从Large Pool中分配的。当sort area大小无法满足排序操作要求时,就会占用


临时表空间来存放排序数据,因而产生Direct Path Read等待事件。我们可以通过适当增加该参数来减少磁盘


排序操作。


这个参数可以在系统范围或会话范围进行修改。对于一些需要做大量排序操作而且又比较独立的会话(如


Create Index),我们可以在会话级别为其设置比较大的Sort Area以满足排序需要:


SQL> alter session set sort_area_size = 10000000;
 
Session altered.
该参数大小一般推荐设置为1~3M。在9i之后,不推荐设置该参数,我们可以通过设置PGA_AGGREGATE_TARGET进


行PGA内存自动管理(设置WORKAREA_SIZE_POLICY为TRUE)。对于PGA_AGGREGATE_TARGET的大小设置,可以参考


文章《Oracle内存全面分析》中的PGA_AGGREGATE_TARGET部分。


此外,我们还可以通过以下语句来查找系统中存在磁盘排序的会话及其语句:


SELECT a.sid,a.value, b.name, d.sql_text from


V$SESSTAT a, V$STATNAME b, V$SESSION c, V$SQLAREA d


WHERE a.statistic#=b.statistic#


AND b.name = 'sorts (disk)'


and a.sid = c.sid


and c.SQL_ADDRESS = d.ADDRESS(+)


and c.SQL_HASH_VALUE = d.HASH_VALUE(+)


and value > 0


ORDER BY 2 desc,1;
3.2.4.2  并行查询
当设置表的并行度非常高时,优化器可能就对表进行并行全表扫描,这时会引起Direct Path Read等待。


在使用并行查询前需要慎重考虑,因为并行查询尽管能教师程序的响应时间,但是会消耗比较多的资源。对于


低配置的数据库服务器不建议使用并行特性。此外,需要确认并行度的设置要与IO系统的配置相符(建议并行


度为2~4 * CPU数)。在10g中,可以考虑使用ASM。


对于表的并行度,我们不建议直接用ALERT修改表的物理并行度:


ALTER TABLE t_test1 PARALLEL DEGREE 16;
而是推荐针对特定语句使用提示来设置表的并行度:


SQL> SELECT /*+ FULL(T) PARALLEL(T, 4)*/ object_name FROM t_test1 t;
 
47582 rows selected.
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2467664162
 
--------------------------------------------------------------------------------
| Id  | Operation    | Name     | Rows  | Bytes | Cost (%CPU)| Time
|    TQ  |IN-OUT| PQ Distrib |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |  | 47582 |  1068K|    42   (3)| 00:00:01
|   | |    |
|   1 |  PX COORDINATOR |  |  |  |    |
|   | |    |
|   2 |   PX SEND QC (RANDOM)| :TQ10000 | 47582 |  1068K|    42   (3)| 00:00:01
|  Q1,00 | P->S | QC (RAND)  |
|   3 |    PX BLOCK ITERATOR |  | 47582 |  1068K|    42   (3)| 00:00:01
|  Q1,00 | PCWC |    |
|   4 |     TABLE ACCESS FULL| T_TEST1  | 47582 |  1068K|    42   (3)| 00:00:01
|  Q1,00 | PCWP |    |
--------------------------------------------------------------------------------
3.2.4.3  Hash Join
Hash Area是用于hash join的内存区域。Hash Area过小会引起Direct Path Read等待。当


WORKAREA_SIZE_POLICY为FALSE时,可以考虑增加hash_area_size的大小(建议为sort_area_size大小的1.5倍


);当WORKAREA_SIZE_POLICY为TRUE时,可以考虑增加PGA_AGGREGATE_TARGET大小。


3.2.4.4  Direct path read (lob)
为了减少LOB的读写时间,通常我们会设置LOB的存储参数NOCACHE,这时读取LOB时会引起Direct Path Read 


(lob)等待事件。但当我们发现Direct path read (lob) 引起了IO性能问题,就需要考虑将那些被经常读取的


LOB字段设置为CACHE。另外,如果操作系统的文件系统有足够的Buffer Cache时可以考虑将LOB数据段存储在文


件系统上。


3.2.4.5  其他优化措施
当内存资源不足、IO读取数据到内存效率远远低于内存中数据被处理的效率时,会引起Direct Path Read等待


事件。作为对上述处理措施的补充,增加内存(PGA)、在确保操作系统支持AIO情况下设置DISK_ASYNCH_IO为


TRUE以支持异步IO、采用效率更高的存储设备都能帮助我们减少Direct Path Read等待。


3.2.5 direct path write & direct path write (lob)
直接写(Direct Path Write)允许一个会话先将IO写请求放入一个队列中,让操作系统去处理IO,而自身可以


继续处理其他操作。当会话需要知道写操作是否完成(如会话需要一块空闲的缓存块或者会话需要确认内存中


所有写操作都被flush到磁盘了),会话就会等待写操作完成从而产生Direct Path Write等待事件。Direct 


Path Write (lob) 是在对LOB数据段(NOCACHE)直接写时产生的等待事件。


在没有启用异步IO时,IO写请求在完成之前会被阻塞,但在执行IO写操作时并不会产生等待事件。进程稍后回


来提取那些已经完成的IO操作数据,这时尽管能够很快返回,但仍然会显示direct path write等待事件。


和Direct Path Read等待事件相似,对Direct Path Write等待事件也要注意以下两点:


等待次数并不等于IO请求次数;
统计(如statspack报告中)得出的Direct Path Write的等待时间并不一定代表该事件引起的真正等待时间。
事件中的P1、P2、P3参数分别代表:


P1:发生等待事件的数据块所在文件号;


P2:发生等待事件的数据块号;


P3:等待事件涉及的连续数据块数量。


直接写请求一般发生在以下几种情况:


直接数据载入操作(如CTAS、SQL*Loader设置Direct选项等);
并行DML操作;
磁盘排序(排序内存空间不足,数据写入磁盘);
载入NOCACHE数据段;
对Direct Path Write的优化处理措施基本上和Direct Path Write类似。


3.5     Buffer Cache相关的IO事件
Buffer Cache是影响Oracle IO的重要因素。这里要解决的几个等待事件都是涉及到DBWR进程和IO从属进程


(Slave)的Buffer Cache操作引起的等待事件。


3.5.1 db file parallel write
该事件和并行DML无关。这个等待事件出现在当DBWR进程提交了多IO请求来并行将Buffer Cache中的脏数据写入


磁盘中后,等待所有提交的IO请求完成。通常是由于操作系统的IO系统导致的该事件的阻滞。


事件中的P1、P2、P3参数分别代表:


P1:(9.2.0.5之前)写入数据的文件号/(9.2.0.5之后)请求次数;


P2:(9.2.0.5之前)写入的数据块号/(9.2.0.5之后)请求中断的次数;


P3:(9.2.0.5之前)请求次数/(9.2.0.5之后)请求发生了Timeout的时间


这一等待事件一般不会显著影响用户会话。但是当用户会话中有很高的“write complete waits”或“free 


buffer waits”事件的等待时间时,说明该事件已经影响到了用户会话。有时候这一事件对操作系统IO的影响


也会影响到进程从同一磁盘读取数据的等待时间。


解决该事件的关键在于减少相关磁盘的IO冲突。如果这事件已经影响到用户会话,我们需要结合其他等待事件


信息,考虑采取均衡热地磁盘负载、提高存储设备IO效率、增加checkpoint间隔、增大Redo log文件等方法来


减低该事件。


3.5.2 db file single write
当DBWR进程请求修改数据文件头,在等待IO请求完成时,会出现db file single write等待事件。


事件中的P1、P2、P3参数分别代表:


P1:写入数据的文件号;


P2:写入的数据块号;


P3:写入的数据块数(一般为1)。


解决这一等待事件的关键还是要处理好磁盘的IO冲突问题,特别是发生该事件所在的磁盘。通过相关SQL的调优


等手段来降低事件发生的磁盘的IO、采用更高效率的存储设备、均衡磁盘IO负载等方法是降低这一等待事件的


主要方法。


3.5.3 write complete waits
当会话对一个正在被写写入磁盘的Buffer数据块发出请求时,需要等待其被写入磁盘完成,这时就会产生write 


complete waits等待事件。


事件中的P1、P2、P3参数分别代表:


P1:要写入数据的文件号;


P2:要写入的数据块号;


P3:无意义


提高Buffer Cache脏数据写入磁盘的效率、提高整体IO效率是降低该等待事件的主要方法:


配置数据库支持AIO;
增加db_writer_processes(支持AIO时)或者db_io_slaves(不支持AIO时)大小以增加DBWR进程;
其他提高IO效率(如采用裸设备等)、减少IO冲突的方法
3.5.4 free buffer waits
当会话在Buffer Cache中找不到空闲buffer块,或者在没有空闲buffer块来建立一致性读时,就会产生free 


buffer waits等待事件。


事件中的P1、P2、P3参数分别代表:


P1:要读取数据的文件号;


P2:要读取的数据块号;


P3:10gR1之前无意义,10gR1后表示在Buffer Cache中LRU和LRUW列表的SET_ID#


这一等待事件通常表示Buffer Cache不足或者从Buffer Cache中将脏数据写入磁盘的效率太低。要降低该等待


事件,我们就需要分别从这两方面入手:调整Buffer Cache的大小(如根据statspack的建议器来设置);按照


我们前述的方法来提高存储设备的IO效率。


4 结束语
最后要说的是,一旦数据库服务器出现了IO问题后,首先要检查操作系统本身的IO系统是否有问题,然后再确


认是否是Oracle出现了IO问题。


其次要注意的一点是,上述等待事件在系统出现一定的等待次数对于系统来说是正常的,我们要解决的是对系


统IO影响最大的一个或几个等待事件,而不是全部事件。


总的来说,要调整Oracle中出现的IO性能问题,我们有两种手段:一种是针对特定等待事件的相应方法,如相


关SQL语句的调优、相关参数的修改;另外一种是通过提升整体IO效率、减少IO冲突来降低IO等待,如均衡IO负


载、使用效率更高的存储设备、激活AIO和重新分布对IO有不同要求的文件。


事实上,数据库的性能问题大多数是由应用引起的,而其中大部分问题都是Top SQL造成。因此,这里要说的一


句题外话就是:SQL调优是每一个DBA必须具备的最基本的技能。因为很多时候无论采用什么手段、什么工具来


定位问题,通过各种内部机制来分析问题,但最终解决问题的手段就是SQL调优。


5 参考文章
1、 www.HelloDBA.com


2、 Metalink.Oracle.com


3、 Oracle OTN


4、 Oracle Concept


5、 Oracle Database Performance Tuning Guide
========

oracle内存分析

http://blog.csdn.net/coolwzjcool/article/details/7446505

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值