buffer cache深度分析3:buffer cache的优化

来源:互联网 发布:c 语言入门经典 编辑:程序博客网 时间:2024/05/18 03:27

4. buffer cache的优化

4.1 buffer cache的设置优化

   buffercache的设置随着oracle版本的升级而不断变化。8i下使用db_block_buffers来设置,该参数表示buffercache中所能够包含的内存数据块的个数;9i以后使用db_cache_size来设置,该参数表示buffercache的总共的容量,可以用字节、K、M为单位来进行设置。而到了10g以后则更加简单,甚至可以不用去单独设置buffercache的大小。因为10g引入了ASMM(Automatic Shared MemoryManagement)这样一个可以进行自我调整的组件,该组件可以自动调整shared pool size、db cachesize等SGA中的组件。只需要设置sga_target参数,则其他组件就能够根据系统的负载和历史信息自动的调整各个部分的大小。要启动ASMM,只需要设置statistics_level为typical或all。

   oracle8.0以前只能设置一种buffercache,而从8.0以后,oracle提供了三种类型的buffercache,分别是default、keep、recyle。keep和recycle是可选的,default必须存在。8i以前使用db_block_buffer设置default、buffer_pool_keep设置keep、buffer_pool_recycle设置recyle。而8i以后使用db_cache_size设置default、db_keep_cache_size设置keep、db_recycle_cache_size设置recycle。10g不能自动设置db_keep_cache_size和db_recycle_cache_size,必须手工设置。
同时,8i以前,这三种buffer cache是独立指定的,互不制约。而8i以后,这三种buffercache是有相互制约关系的。如果指定了keep和recycle的buffer cache,则default类型的buffercache的大小就是db_cache_size - buffer_pool_keep -buffer_pool_recycle。

    通常将经常访问的对象放入keep类型的buffercache里,而将不常访问的大表放入recycle类型的buffer cache里。其他没有指定buffercache类型的对象都将进入default类型的buffer cache里。为对象指定buffer cache类型的方法如下:

SQL>createtable test (nnumber) storage (buffer_pool keep); SQL> altertable test storage (buffer_poolrecycle);

  如果没有指定buffer_pool短语,则表示该对象进入default类型的buffercache。

  这里要说明的是,从名字上看,很容易让人误以为这三种buffercache提供了三种不同的管理内存数据块的机制。但事实上,它们之间在管理和内部机制上没有任何的区别。它们仅仅是为DBA们提供了一个选择,就是能够将数据库对象分成“非常热的”、“比较热的”和“不热的”这三种类型。因为数据库中总会存在一些“非常热”的对象,它们频繁的被访问。而如果某个时候系统偶尔做了一次大表的全表扫描,就有可能将这些对象清除出内存。为了防止这种情况的发生,我们可以设置keep类型的buffercache,并将这种对象都移入keep buffercache中。同样的,数据库中也总会有一些很大的表,可能每天为了生成一张报表,而只需要访问一次就可以了。但有可能就是这么一次访问,就将大部分的内存数据块清除出了buffercache。为了避免这种情况的发生,可以设置recycle类型的buffer cache,并将这种偶尔访问的大表移入recyclebuffer cache。

  毫无疑问,如果你要设置这三种类型的buffercache,你需要自己研究并等于你的数据库中的对象进行分类,并计算这些对象的大小,从而才能够正确的把它们放入不同的buffercache。但是,不管怎么说,设置这三种类型的buffercache只能算是最低层次的优化,也就是说在你没有任何办法的情况下,可以考虑设置他们。但是如果你能够优化某条buffergets非常高SQL使其buffer gets降低50%的话,就已经比设置多个buffer cache要好很多了。

   9i以后还提供了可以设置多种数据块尺寸(2、4、8、16 或 32k)的buffercache,以便存放不同数据块尺寸的表空间中的对象。使用初始化参数:db_Nk_cache_size来指定不同数据块尺寸的buffercache,这里的N就是2、4、8、16 或32。创建数据库时,使用初始化参数:db_block_size所指定缺省的数据块尺寸用于system表空间。然后可以指定最多4个不同数据块尺寸的表空间,每种数据块尺寸的表空间必须对应一种不同尺寸的buffercache,否则不能创建不同数据块尺寸的表空间。

SQL>createtablespace tbs_test_16k 2 datafile 'C:\oracle\oradata\ora92\tbs_test_16k.dbf'size 10M3 blocksize 16k; createtablespace tbs_test_16k*ERROR位于第1 行: ORA-29339:表空间块大小16384与配置的块大小不匹配 SQL> show parameter db_16k_cache_size NAME TYPE VALUE ----------------------------------------------- ------------------------------db_16k_cache_sizebiginteger0

  我们可以看到,由于16k数据块所对应的buffercache没有指定,所以创建16k数据块的表空间会失
败。于是我们先设置db_16k_cache_size,然后再试着创建16k数据块的表空间。

SQL>altersystem set db_16k_cache_size=10M; 系统已更改。 SQL> createtablespace tbs_test_16k 2 datafile 'C:\oracle\oradata\ora92\tbs_test_16k.dbf'size 10M3 blocksize 16k; 表空间已创建。

  不同尺寸数据块的buffer cache的管理和内部机制与缺省数据块的buffercache没有任何的分别。它最大的好处是,当使用可传输的表空间从其他数据库中将不同于当前缺省数据块尺寸的表空间传输过来的时候,可以不做很多处理的直接导入到当前数据库,只需要设置对应的数据块尺寸的buffercache即可。同时,它对于调优OLTP和OLAP混合的数据库也有一定的用处。OLTP环境下,倾向于使用较小的数据块,而OLAP环境下,由于基本都是执行全表扫描,因此倾向于使用较大的数据块。这时,可以将OLAP的表转移到使用大数据块(比如32k)的表空间里去。而将OLTP的表放在中等大小的数据块(比如8k)的表空间里。
对于应该设置buffercache为多大,oracle从9i开始通过设置初始化参数:db_cache_advice,从而提供了可以参照的建议值。oracle会监控default类型、keep类型和recycle类型的buffercache的使用,以及其他五种不同数据库尺寸(2、4、8、16 或 32k)的buffercache的使用。在典型负荷的时候,启用该参数,从而收集数据帮助用户确定最佳的db_cache_size的大小。该参数有三个值:
   1) off:不收集数据。
  2) on:开始分配内存收集数据,有可能引发CPU和内存的负担,可能引起4031错。
  3) ready:不收集数据,但是收集数据的内存已经预先分配好了。通过把该参数值从off设置为ready,然后再设置为on,就可以避免出现4031错。

    oracle会根据当前所监控到的物理读的速率,从而估算出在不同大小尺寸的buffercache下,所产生的可能的物理读的数量。oracle会将这些收集到的信息放入视图:v$db_cache_advice中。每种类型的buffercache都会有相应的若干条记录来表示所建议的buffercache的大小。比如下面,我们显示对于缺省类型的、缺省数据块尺寸的buffer cache的建议大小应该是多少。

SQL>SELECTsize_for_estimate, buffers_for_estimate,2 estd_physical_read_factor,estd_physical_reads3 FROMv$db_cache_advice 4 WHERENAME = 'DEFAULT'5ANDblock_size =(SELECT VALUE 6 FROMv$parameter 7 WHERENAME = 'db_block_size')8 /SIZE_FOR_ESTIMATEBUFFERS_FOR_ESTIMATE ESTD_PHYSICAL_READ_FACTOR ESTD_PHYSICAL_READS------------------------------------- --------------------------------------------45001.386940154810001.3848400931215001.1861343391620001.1397329962025001289522430001289522835001289523240001289523645000.8671251044050000.8671251044455000.8671251044860000.7422214885265000.7422214885670000.7422214886075000.554160406480000.554160406885000.554160407290000.554160407695000.5541604080100000.55416040

   这里的字段estd_physical_read_factor表示在相应的buffercache尺寸(由字段size_for_estimate表示)
 下,估计从硬盘里读取数据的次数除以在内存里读取数据的次数。如果没有发生物理读则该比值为空。在
 内存足够的前提下,这个比值应该是越低越好的。从上面的输出中,我们可以看到,如果将buffercache
设置为60M,可以获得较好的性能,物理读也将会有一个显著的下降。但是设置为大于60M的话(比如
64M或68M),则不会降低物理读,反而浪费内存空间。所以从上面的查询结果中,我们可以知道,设置为60M是比较合适的。

4.2 buffercache的统计信息

  为了对buffer cache进行性能的诊断,oracle提供了很多有关buffercache的统计信息。这些统计信息大致可以分成三类:1)有关用户发出的对内存数据块的请求相关的统计信息;2)有关DBWR后台进程对内存数据块处理相关的统计信息;3)RAC相关的统计信息。

     我们在诊断buffercache时,不需要关注所有的统计信息。这里主要介绍几个重要的统计信息,其他的统计信息都可以到《Oracle9i DatabaseReference: Appendix C》中找到。如下所示:

SQL>SELECTname, value FROMv$sysstat WHEREname in ( 2 'session logical reads',3 'physical reads',4 'physical reads direct',5 'physical reads direct (lob)',6 'consistent gets',7 'db block gets',8 'free buffer inspected')9 /NAMEVALUE--------------------------------------------------------------------------sessionlogical reads 73797dbblock gets 498consistentgets73299physicalreads29017freebuffer inspected 0physicalreads direct 40
  这里做些简单的解释。
     1) sessionlogicalreads:所有的逻辑读的数据块的数量。注意,其中包括先从硬盘上读数据块到内存里,再从内存里读数据块。
   2) consistent gets:在一致性(consistentread)读模式下读取的内存里的数据块数量。包括从rollback segment里读取的数据块数量以及从data blockbuffer里读取的数据块数量。主要是通过select产生的。Update/delete也能产生很少量的此类数据块。注意:如果oracle的运行时间过长,由于oracle的bug导致consistentgets大大超过实际的数量。因此建议使用‘no work - consistent read gets’, ‘cleanoutsonly - consistent read gets’,‘rollbacks only - consistent readgets’, ‘cleanouts and rollbacks - consistent readgets’之和来代替consistent gets的值。
   3) db blockgets:在当前(current)模式下读取的内存里的数据块的数量。不是读取过去某个时点的数据块,而必须是当前最新的数据块。主要是通过update/delete/insert来产生的,因为DML需要当前最新的数据块才能对之进行改变。在字典管理表空间下,一些获得当前可用扩展空间的select语句也会产生此类数据块,因为必须得到当前最新的空间使用信息才能扩展。逻辑上,sessionlogical reads = consistent gets + db block gets。
   4) physicalreads:从硬盘里读取的数据块的数量。注意,这个数量大于实际从硬盘里读取的数量,因为这部分block也包括了从操作系统缓存里读取的数据块数量。
   5) physical readsdirect:有些数据块不会先从硬盘读入内存再从内存读入PGA再传给用户,而是绕过SGA直接从硬盘读入PGA。比如并行查询以及从临时表空间读取数据。这部分数据块由于不缓存使得hitratio不会被提高。
   6) physical reads direct (lob):与physical readsdirect一样。
   7) free bufferinspected:这个值表示为了找到可用数据块而跳过的数据块的数量。这些被跳过的数据块就是脏的或被锁定的数据块。明显,这个值如果持续增长或很高,就需要增加buffercache的大小了。

  在获得了这些统计信息以后,我们可以计算buffer cache的命中率:
1HitRatio=1– (physical reads – physicalreads direct-physical reads direct (lob))/session logical reads 2Miss ratio = (physical reads – physical readsdirect-physicalreads direct (lob) ) / session logical reads

   通常在OLTP下,hitratio应该高于0.9。否则如果低于0.9则需要增加buffer cache的大小。在考虑
 调整buffer cache hit ratio时,需要注意以下几点。
   1) 如果上次增加buffer cache的大小以后,没有对提高hitratio产生很大效果的话,不要盲目增加buffercache的大小以提高性能。因为对于排序操作或并行读,oracle是绕过buffer cache进行的。
   2) 在调整buffer cache时,尽量避免增加很多的内存而只是提高少量hitratio的情况出现。
我们还可以查询每种buffercache的统计信息,主要关注的还是consistent_gets和db_block_gets以及
 physical_reads的值。

SQL>SELECTname, block_size,physical_reads,db_block_gets,consistent_gets2 FROMv$buffer_pool_statistics; NAME BLOCK_SIZE PHYSICAL_READS DB_BLOCK_GETS CONSISTENT_GETS------------------------------ -------------- ----------------------------DEFAULT81922897871977591DEFAULT1638428011
     v$sysstat中名称以DBWR开头的都是有关DBWR后台进程相关的统计信息。当DBWR进程写完脏数据块以后或者扫描完LRU链表以后更新这些统计信息。DBWR会基于被触发的频率以及所处理的内存数据块的数量与总内存数据块的数量的比例,来进行自我调整。我们可以通过这些统计信息得到一些对当前DBWR运行情况的认识。

 

4.3 buffer cache的等待事件
    与buffercache相关的等待事件包括:latch free、buffer busy waits、free bufferwaits。曾经发生过的等
待事件可以从v$system_event(一个等待事件对应一行记录)和v$session_event(一个session一个等待事件对应一行记录)中看到。而当前系统正在经历的等待事件可以从v$session_wait看到。

4.3.1 latch free等待

    等待事件“latch free”中与buffer cache有关的有两类:cache buffers chainslatch和cache buffers lru chain latch。在理解了上面所描述的有关buffercache的内部管理机制以后,就应该很容易理解这两个latch产生的原因。

    对于buffercache中的每个hash chain链表来说,都会有一个名为cache buffers chainslatch的latch来保护对hash chain的并发操作,这种latch通常也叫作hash latch或CBClatch。数据库中会有很多的cache buffers chains latch,每个latch都叫做child cachebuffers chains latch。一个child cache buffers chains latch会管理多个hashchain。前面我们知道,hashchain的数量由一个隐藏参数:_db_block_hash_buckets决定。同样也有一个隐藏参数:_db_block_hash_latches来决定有多少个cachebuffers chains latch来管理这些hash chain。该参数的缺省值由buffercache中所含有的内存数据块的多少决定,当内存数据块的数量
    •少于2052个时,_db_block_hash_latches = power(2,trunc(log(2, 内存块数量 - 4)- 1))
   •多于131075个时,_db_block_hash_latches = power(2,trunc(log(2,db_block_buffers - 4) - 6))
   •位于2052与131075 buffers之间,_db_block_hash_latches = 1024
可以使用下面的SQL语句来确定当前系统的cache buffers chains latch的数量。
SQL>selectcount(distinct(hladdr))from x$bh; COUNT(DISTINCT(HLADDR))-----------------------1024SQL>selectcount(*)from v$latch_childrenwhere name='cache buffers chains';COUNT(*)----------1024
    在知道了cache buffers chains latch的数量以后,我们只需要用hashchain的数量除以latch的数量以后,就可以算出每个latch管理多少个hashchain了。我们将下面7532除以1024,就可以知道,当前的系统中,每个latch大概对应8个hashchain。
SQL>selectx.ksppinm, y.ksppstvl, x.ksppdesc 2 fromx$ksppi x , x$ksppcv y 3 wherex.indx = y.indx 4 and x.ksppinm like '\_%'escape '\'5andksppinm like'%_db_block_hash_buckets%'6; KSPPINM KSPPSTVL KSPPDESC ------------------------------ -------------------------------------_db_block_hash_buckets7523Numberofdatabase block hash buckets
    当数据库在hashchain搜索需要的数据块时,必须先获得cache buffers chains latch。然后在扫描hashchain的过程中会一直持有该latch,直到找到所要的数据块才会释放该latch。当有进程一直在扫描某条hashchain,而其他进程也要扫描相同的hash chain时,其他进程就必须等待类型为cache buffers chainslatch的latch free等待事件。

   不够优化的SQL语句是导致cache buffers chainslatch的主要原因。如果SQL语句需要访问过多的内存数据块,那么必然会持有latch很长时间。找出逻辑读特别大的sql语句进行调整。v$sqlarea里那些buffer_gets/executions为较大值的SQL语句就是那些需要调整的SQL语句。这种方式不是很有针对性,比较盲目。网上曾经有人提供了一个比较有针对性的、查找这种引起较为严重的cachebuffers chains latch的SQL语句的方式,其原理是根据latch的地址,到x$bh中找对应的bufferheader,x$bh的hladdr表示该buffer header所对应的latch地址。然后根据bufferheader可以找到所对应的表的名称。最后可以到v$sqltext(也可以到stats$sqltext)中找到引用了这些表的SQL语句。我也列在这里。where条件中的rownum<10主要是为了不要返回太多的行,只要能够处理掉前10个latch等待就能有很大改观。
selects.sql_text fromx$bh a,dba_extents b, (select*from (selectaddr from v$latch_children wherename = 'cache buffers chains'order by sleeps desc) whererownum<11) c, v$sqltext s wherea.hladdr= c.addr anda.dbarfil= b.relative_fno anda.dbablkbetween b.block_idand b.block_id+ b.blocks ands.sql_textlike '%'||b.segment_name||'%'and b.segment_type='TABLE'orderbys.hash_value,s.address,s.piece /
   还有一个原因可能会引起cache buffers chainslatch,就是热点数据块问题。这是指多个session重复访问一个或多个被同一个child cache buffers chainslatch保护的内存数据块。这主要是应用程序的问题。大多数情况下,单纯增加child cache buffers chainslatches的个数对提高性能没有作用。这是因为内存数据块是根据数据块地址以及hashchain的个数来进行hash运算从而得到具体的hash chain的,而不是根据child cache buffers chainslatches的个数。如果数据块的地址以及hash chain的个数保持一致,那么热点块仍然很有可能会被hash到同一个childcache buffers chains latch上。可以通过v$session_wait的p1raw字段来判断latchfree等待事件是否是由于出现了热点块。如果p1raw保持一致,那么说明session在等待同一个latch地址,系统存在热点块。当然也可以通过x$bh的tch来判断是否出现了热点块,该值越高则数据块越热。
SQL>selectsid, p1raw, p2, p3, seconds_in_wait,wait_time, state 2 fromv$session_wait 3 whereevent = 'latch free'4orderby p2, p1raw; SID P1RAW P2 P3 SECONDS_IN_WAIT WAIT_TIME STATE ------------ --- --- --------------- ----------------------------386666535C13 1 1 2 WAITED KNOWN TIME 42 6666535C 13 1 1 2 WAITED KNOWN TIME 44 6666535C 13 3 1 4 WAITED KNOWN TIME ……………………… 85 6666535C 13 3 1 12 WAITED KNOWN TIME 214 6666535C 138 1 1 2 WAITED KNOWN TIME
接下来,我们就可以根据p1raw的值去找到所对应的内存数据块以及对应的表的名称了。
selecta.hladdr, a.file#, a.dbablk, a.tch, a.obj,b.object_namefromx$bh a, dba_objects b where(a.obj = b.object_idor a.obj = b.data_object_id) anda.hladdr= '6666535C';
    要解决热点块的问题,可以通过将热点块中的行分散到多个数据块中去,这样原来的热点块就变成了多个数据块,这样被hash到同一个latch的几率就降低了。如果热点块属于表,则可以先将表的数据导出来,然后增加表的pctfree值,最后将数据再导入。如果热点块属于索引,则可以设定较高的pctfree参数后,重建索引。注意,这会增加索引的高度。

   通过前面我们已经知道,每个working set都会有一个名为cache buffers lru chain的latch(也叫做lrulatch)来管理。任何要访问working set的进程都必须先获得cache buffers lru chainlatch。cache buffers lru chainlatch争用也是由于低效的扫描过多的内存数据块的SQL语句引起的。调整这些语句以降低逻辑读和物理读。只要修改一下上面找引起cachebuffers chains latch的SQL语句即可找到这样的SQL语句。
selects.sql_text fromx$bh a,dba_extents b, (select*from (selectaddr from v$latch_children wherename = 'cache buffers lru chain'order by sleeps desc) whererownum<11) c, v$sqltext s wherea.hladdr= c.addr anda.dbarfil= b.relative_fno anda.dbablkbetween b.block_idand b.block_id+ b.blocks ands.sql_textlike '%'||b.segment_name||'%'and b.segment_type='TABLE'orderbys.hash_value,s.address,s.piece /

4.3.2 buffer busy waits等待

     当一个session在读取或修改buffer cache里的内存数据块时,首先必须获得cache buffers chainslatch,获得以后,到hash chain上遍历直到找到需要的bufferheader后。这时,该session必须在该bufferheader上以share或exclusive模式(具体哪个模式由该session的操作决定)获得一个bufferlock或一个buffer pin。一旦buffer header被pin住,session就将释放cache bufferschains latch,然后可以在该buffer上进行操作了。如果无法获得bufferpin,那么该session就会等待buffer busywaits等待事件。该等待事件不会出现在session的私有PGA里。

    buffer busy waits等待事件不能像latchfree等待那样可以相对比较容易的进行事后跟踪。对于该等待事件,oracle提供了v$waitstat视图。v$waitstat里的记录都是bufferbusy waits等待事件发生时进行更新的。也就是说,该视图体现的都是buffer busywaits等待事件的统计数据。但这只能给你提供一个大概的buffer busywaits的分布。如果要想具体的诊断该等待事件,只能当发生该等待时,到v$session_wait里去找原因,从而才能找到解决的办法。处理bufferbusy wait等待事件时,首先使用下面的SQL语句找到发生等待的数据块类别以及对应的segment。

select'Segment Header' class, a.segment_type, a.segment_name, a.partition_name fromdba_segments a, v$session_wait bwherea.header_file=b.p1 anda.header_block=b.p2 andb.event = 'buffer busy waits'unionselect'Freelist Groups' class, a.segment_type, a.segment_name, a.partition_name fromdba_segments a, v$session_wait bwhereb.p2 between a.header_block + 1 and (a.header_block + a.freelist_groups) anda.header_file=b.p1 anda.freelist_groups>1andb.event= 'buffer busy waits'unionselecta.segment_type||' block'class, a.segment_type, a.segment_name, a.partition_name fromdba_extents a, v$session_wait bwhereb.p2 between a.block_id and a.block_id + a.blocks - 1anda.file_id= b.p1 andb.event = 'buffer busy waits'andnotexists (select1fromdba_segments whereheader_file= b.p1 andheader_block= b.p2);

  然后,根据不同的数据块类型进行相应的处理。
     1) 如果数据块类型为datablock,如果版本为10g之前,则可以同时参照p3列的值来共同诊断。如果p3为130意味着同时有很多session在访问同一个datablock,而且该data block没有在内存里,而必须从磁盘上获取。有三种方法可以降低该事件出现的频率:
    a、降低并发性。这个比较难实现。
   b、找出并优化含有这些segment的SQL语句,以降低物理和逻辑读。
   c、增加freelists和freelist groups。
如果没有足够的freelists,当同时对同一个表进行insert时,这就很容易引起buffer busywaits等待。如果正在等待buffer busywaits的session正在进行insert操作,那么需要检查以下那个表有多少freelists了。当然,由于freelists的不足主要会导致对于segmentheader的buffer busy waits等待。

   如果p3为220意味着有多个session同时修改在一个block(该block已经被读入内存了)里的不同的行。这种情况通常出现在高DML并发性的环境里。有三种方法可以降低该事件出现的频率:
   a、降低并发性。这个比较难实现。
   b、通过增加pctfree减少block里含有的行数。
   c、将该对象移到拥有较小block尺寸的表空间里(9i或以上)。

  2) 如果数据块类型为data segment header(表或索引的segmentheader,不是undo segment header)上发生buffer busywaits等待事件,通常表明数据库里有些表或索引的段头具有频繁的活动。
进程访问segment header主要有两种原因:一是获得或修改processfreelists信息;二是扩展HWM。有三种方法可以降低该事件出现的频率:
   a、增加争用对象的freelists和freelist groups的数量。
   b、确定pctfree和pctused之间的间隔不要太小。
    c、确保nextextent的尺寸不要太小。
   d、9i以后,使用ASSM特性来管理block。

   3) 如果数据块类型为undo segmentheaders的争用等待,表明数据库中的rollback segments太少,或者他们的extentsize太小,导致对于同一个segment header的大量更新。如果使用了9i以后的auto undomanagement,则不用处理,因为oracle会根据需要自动创建新的undosegments。如果是9i之前,则可以创建新的private rollbacksegments,并把它们online,或者通过降低transactions_per_rollback_segment参数来减轻该等待。

   4) 如果数据块类型为undoblock,说明有多个session同时访问那些被更新过的block。这是应用系统的问题,在数据库来说对此无能为力。

4.3.3 free bufferwaits等待

   在一个数据块被读入buffer cache之前,oracle进程必须为该数据块获得一个对应的可用的内存数
据块。当session在LRUlist上无法发现一个可用的内存数据块或者搜寻可用的内存数据块被暂停的时候,该session就必须等待free bufferwaits事件。

  从前面的描述,我们已经知道,一个需要可用内存数据块的前台进程会连续扫描LRU链表,直到达到一个限定值(也就是隐藏参数_db_block_max_scan_pct所指定的值,表示已经扫描的bufferheader数量占整个LRU链表上的bufferheader的总数量,在9i中该限定值为40%)。如果到该限定值时还没找到可用内存数据块时,该前台进程就会触发DBWR进程以便清空一些脏数据块,从而使得在辅助LRU链表上能够挂上一些可用的内存数据块。在DBWR进程工作时,该前台进程就必须等待freebuffer waits。

   oracle跟踪每次对于可用的内存数据块的请求次数(记录在v$sysstat里的free bufferrequested),也跟踪每次请求可用的内存数据块失败的次数(记录在v$system_event里的free bufferwaits的total_waits)。而v$sysstat里的free bufferinspected则说明oracle为了找到可用的内存数据块所所跳过的数据块的个数,如果buffercache很空,有很多空的数据块的话,则该值为0。如果free buffer inspected相对free bufferrequested来说很高,则说明oracle进程需要扫描更多的LRU链表上的数据块才可以找到可用的数据块。

SQL>select*2fromv$sysstat 3 wherename in ('free buffer requested','free buffer inspected'); STATISTIC# NAME CLASS VALUE ---------------------------------------- ---------------------75free buffer requested829053249379free buffer inspected82983596SQL>select*2fromv$system_event 3 whereevent = 'free buffer waits'; EVENT TOTAL_WAITS TOTAL_TIMEOUTS TIME_WAITED AVERAGE_WAITTIME_WAITED_MICRO ---------------------------- -------------- ----------- -----------------------------freebuffer waits 10034767107571710749256
 

  可以看到,该系统的free bufferwaits等待很少,总共等待的时间才0.476秒。同时也可以看到,请求了290532493(free bufferrequested)个可用的内存数据块,但是在这个过程中只跳过了2983596(free bufferinspected)个数据块,二者相差2个数量级。说明系统很容易就找到可用的内存数据块。

   如果一个session花费了很多的时间等待freebuffer waits等待事件的话,通常可能有以下原因:
   1) 低效率的SQL语句:对于那些引起很大逻辑读的SQL语句(v$sql里的disk_reads),那些SQL语句可能进行了全表扫描,索引全扫描、或者通过了不正确的索引扫描表等。调整这些SQL语句以降低逻辑读。
   2) DBWR进程不够多:也可以通过增加DBWR checkpoints的个数来降低freebufferwaits。9i下,可以通过减小fast_start_mttr_target参数来缩短MTTR,从而增加DBWR进程启动的次数。然而,这也有可能引起进程等待writecomplete waits事件。
   3) I/O子系统太慢。
   4) 延迟的块清除(blockclearouts):通常发生的情形是,晚上向数据库导入了一个很大的表。然后早上运行应用系统时,会发现有有进程在等待bufferbusy waits。这是因为第一个访问该表的进程将进行一个延迟的块清除,而这会导致free bufferwaits等待事件。解决方法是在导入表完毕以后,执行一句全表扫描,比如通常是:select count(*)from该大表。这样在后面的进程再次访问的时候就不会产生free buffer waits等待事件了。
   5) buffer cache太小:遇到free bufferwaits事件,首先想到的就是增加buffer cache的大小。