【IT168技术文档】
接上一篇;
4. buffer cache的优化
4.1 buffer cache的设置优化
buffer cache的设置随着oracle版本的升级而不断变化。8i下使用db_block_buffers来设置,该参数表示buffer cache中所能够包含的内存数据块的个数;9i以后使用db_cache_size来设置,该参数表示buffer cache的总共的容量,可以用字节、K、M为单位来进行设置。而到了10g以后则更加简单,甚至可以不用去单独设置buffer cache的大小。因为10g引入了ASMM(Automatic Shared Memory Management)这样一个可以进行自我调整的组件,该组件可以自动调整shared pool size、db cache size等SGA中的组件。只需要设置sga_target参数,则其他组件就能够根据系统的负载和历史信息自动的调整各个部分的大小。要启动ASMM,只需要设置statistics_level为typical或all。
oracle8.0以前只能设置一种buffer cache,而从8.0以后,oracle提供了三种类型的buffer cache,分别是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以后,这三种buffer cache是有相互制约关系的。如果指定了keep和recycle的buffer cache,则default类型的buffer cache的大小就是db_cache_size - buffer_pool_keep - buffer_pool_recycle。
通常将经常访问的对象放入keep类型的buffer cache里,而将不常访问的大表放入recycle类型的buffer cache里。其他没有指定buffer cache类型的对象都将进入default类型的buffer cache里。为对象指定buffer cache类型的方法如下:
SQL> create table test (n number) storage (buffer_pool keep); SQL> alter table test storage (buffer_pool recycle);
如果没有指定buffer_pool短语,则表示该对象进入default类型的buffer cache。
这里要说明的是,从名字上看,很容易让人误以为这三种buffer cache提供了三种不同的管理内存数据块的机制。但事实上,它们之间在管理和内部机制上没有任何的区别。它们仅仅是为DBA们提供了一个选择,就是能够将数据库对象分成“非常热的”、“比较热的”和“不热的”这三种类型。因为数据库中总会存在一些“非常热”的对象,它们频繁的被访问。而如果某个时候系统偶尔做了一次大表的全表扫描,就有可能将这些对象清除出内存。为了防止这种情况的发生,我们可以设置keep类型的buffer cache,并将这种对象都移入keep buffer cache中。同样的,数据库中也总会有一些很大的表,可能每天为了生成一张报表,而只需要访问一次就可以了。但有可能就是这么一次访问,就将大部分的内存数据块清除出了buffer cache。为了避免这种情况的发生,可以设置recycle类型的buffer cache,并将这种偶尔访问的大表移入recycle buffer cache。
毫无疑问,如果你要设置这三种类型的buffer cache,你需要自己研究并等于你的数据库中的对象进行分类,并计算这些对象的大小,从而才能够正确的把它们放入不同的buffer cache。但是,不管怎么说,设置这三种类型的buffer cache只能算是最低层次的优化,也就是说在你没有任何办法的情况下,可以考虑设置他们。但是如果你能够优化某条buffer gets非常高SQL使其buffer gets降低50%的话,就已经比设置多个buffer cache要好很多了。
9i以后还提供了可以设置多种数据块尺寸(2、4、8、16 或 32k)的buffer cache,以便存放不同数据块尺寸的表空间中的对象。使用初始化参数:db_Nk_cache_size来指定不同数据块尺寸的buffer cache,这里的N就是2、4、8、16 或 32。创建数据库时,使用初始化参数:db_block_size所指定缺省的数据块尺寸用于system表空间。然后可以指定最多4个不同数据块尺寸的表空间,每种数据块尺寸的表空间必须对应一种不同尺寸的buffer cache,否则不能创建不同数据块尺寸的表空间。
SQL> create tablespace tbs_test_16k 2 datafile 'C:\oracle\oradata\ora92\tbs_test_16k.dbf' size 10M 3 blocksize 16k; create tablespace tbs_test_16k * ERROR 位于第 1 行: ORA-29339: 表空间块大小 16384 与配置的块大小不匹配 SQL> show parameter db_16k_cache_size NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ db_16k_cache_size big integer 0
我们可以看到,由于16k数据块所对应的buffer cache没有指定,所以创建16k数据块的表空间会失
败。于是我们先设置db_16k_cache_size,然后再试着创建16k数据块的表空间。
SQL> alter system set db_16k_cache_size=10M; 系统已更改。 SQL> create tablespace tbs_test_16k 2 datafile 'C:\oracle\oradata\ora92\tbs_test_16k.dbf' size 10M 3 blocksize 16k; 表空间已创建。
不同尺寸数据块的buffer cache的管理和内部机制与缺省数据块的buffer cache没有任何的分别。它最大的好处是,当使用可传输的表空间从其他数据库中将不同于当前缺省数据块尺寸的表空间传输过来的时候,可以不做很多处理的直接导入到当前数据库,只需要设置对应的数据块尺寸的buffer cache即可。同时,它对于调优OLTP和OLAP混合的数据库也有一定的用处。OLTP环境下,倾向于使用较小的数据块,而OLAP环境下,由于基本都是执行全表扫描,因此倾向于使用较大的数据块。这时,可以将OLAP的表转移到使用大数据块(比如32k)的表空间里去。而将OLTP的表放在中等大小的数据块(比如8k)的表空间里。
对于应该设置buffer cache为多大,oracle从9i开始通过设置初始化参数:db_cache_advice,从而提供了可以参照的建议值。oracle会监控default类型、keep类型和recycle类型的buffer cache的使用,以及其他五种不同数据库尺寸(2、4、8、16 或 32k)的buffer cache的使用。在典型负荷的时候,启用该参数,从而收集数据帮助用户确定非常好的的db_cache_size的大小。该参数有三个值:
1) off:不收集数据。
2) on:开始分配内存收集数据,有可能引发CPU和内存的负担,可能引起4031错。
3) ready:不收集数据,但是收集数据的内存已经预先分配好了。通过把该参数值从off设置为ready,然后再设置为on,就可以避免出现4031错。
oracle会根据当前所监控到的物理读的速率,从而估算出在不同大小尺寸的buffer cache下,所产生的可能的物理读的数量。oracle会将这些收集到的信息放入视图:v$db_cache_advice中。每种类型的buffer cache都会有相应的若干条记录来表示所建议的buffer cache的大小。比如下面,我们显示对于缺省类型的、缺省数据块尺寸的buffer cache的建议大小应该是多少。
SQL> SELECT size_for_estimate, buffers_for_estimate, 2 estd_physical_read_factor,estd_physical_reads 3 FROM v$db_cache_advice 4 WHERE NAME = 'DEFAULT' 5 AND block_size = (SELECT VALUE 6 FROM v$parameter 7 WHERE NAME = 'db_block_size') 8 / SIZE_FOR_ESTIMATE BUFFERS_FOR_ESTIMATE ESTD_PHYSICAL_READ_FACTOR ESTD_PHYSICAL_READS ----------------- -------------------- ------------------------- ------------------- 4 500 1.3869 40154 8 1000 1.3848 40093 12 1500 1.1861 34339 16 2000 1.1397 32996 20 2500 1 28952 24 3000 1 28952 28 3500 1 28952 32 4000 1 28952 36 4500 0.8671 25104 40 5000 0.8671 25104 44 5500 0.8671 25104 48 6000 0.7422 21488 52 6500 0.7422 21488 56 7000 0.7422 21488 60 7500 0.554 16040 64 8000 0.554 16040 68 8500 0.554 16040 72 9000 0.554 16040 76 9500 0.554 16040 80 10000 0.554 16040
这里的字段estd_physical_read_factor表示在相应的buffer cache尺寸(由字段size_for_estimate表示)
下,估计从硬盘里读取数据的次数除以在内存里读取数据的次数。如果没有发生物理读则该比值为空。在
内存足够的前提下,这个比值应该是越低越好的。从上面的输出中,我们可以看到,如果将buffer cache
设置为60M,可以获得较好的性能,物理读也将会有一个显著的下降。但是设置为大于60M的话(比如
64M或68M),则不会降低物理读,反而浪费内存空间。所以从上面的查询结果中,我们可以知道,设置
为60M是比较合适的。
4.2 buffer cache的统计信息
为了对buffer cache进行性能的诊断,oracle提供了很多有关buffer cache的统计信息。这些统计信息大致可以分成三类:1)有关用户发出的对内存数据块的请求相关的统计信息;2)有关DBWR后台进程对内存数据块处理相关的统计信息;3)RAC相关的统计信息。
我们在诊断buffer cache时,不需要关注所有的统计信息。这里主要介绍几个重要的统计信息,其他的统计信息都可以到《Oracle9i Database Reference: Appendix C》中找到。如下所示:
这里做些简单的解释。SQL> SELECT name, value FROM v$sysstat WHERE name 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 / NAME VALUE ---------------------------------------------------------------- ---------- session logical reads 73797 db block gets 498 consistent gets 73299 physical reads 29017 free buffer inspected 0 physical reads direct 40
1) session logical reads:所有的逻辑读的数据块的数量。注意,其中包括先从硬盘上读数据块到内存里,再从内存里读数据块。
2) consistent gets:在一致性(consistent read)读模式下读取的内存里的数据块数量。包括从rollback segment里读取的数据块数量以及从data block buffer里读取的数据块数量。主要是通过select产生的。Update/delete也能产生很少量的此类数据块。注意:如果oracle的运行时间过长,由于oracle的bug导致consistent gets大大超过实际的数量。因此建议使用‘no work - consistent read gets’, ‘cleanouts only - consistent read gets’,‘rollbacks only - consistent read gets’, ‘cleanouts and rollbacks - consistent read gets’之和来代替consistent gets的值。
3) db block gets:在当前(current)模式下读取的内存里的数据块的数量。不是读取过去某个时点的数据块,而必须是当前最新的数据块。主要是通过update/delete/insert来产生的,因为DML需要当前最新的数据块才能对之进行改变。在字典管理表空间下,一些获得当前可用扩展空间的select语句也会产生此类数据块,因为必须得到当前最新的空间使用信息才能扩展。逻辑上,session logical reads = consistent gets + db block gets。
4) physical reads:从硬盘里读取的数据块的数量。注意,这个数量大于实际从硬盘里读取的数量,因为这部分block也包括了从操作系统缓存里读取的数据块数量。
5) physical reads direct:有些数据块不会先从硬盘读入内存再从内存读入PGA再传给用户,而是绕过SGA直接从硬盘读入PGA。比如并行查询以及从临时表空间读取数据。这部分数据块由于不缓存使得hit ratio不会被提高。
6) physical reads direct (lob):与physical reads direct一样。
7) free buffer inspected:这个值表示为了找到可用数据块而跳过的数据块的数量。这些被跳过的数据块就是脏的或被锁定的数据块。明显,这个值如果持续增长或很高,就需要增加buffer cache的大小了。
在获得了这些统计信息以后,我们可以计算buffer cache的命中率:
1Hit Ratio = 1 – (physical reads – physical reads direct - physical reads direct (lob) ) / session logical reads 2Miss ratio = (physical reads – physical reads direct- physical reads direct (lob) ) / session logical reads
通常在OLTP下,hit ratio应该高于0.9。否则如果低于0.9则需要增加buffer cache的大小。在考虑
调整buffer cache hit ratio时,需要注意以下几点。
1) 如果上次增加buffer cache的大小以后,没有对提高hit ratio产生很大效果的话,不要盲目增加buffer cache的大小以提高性能。因为对于排序操作或并行读,oracle是绕过buffer cache进行的。
2) 在调整buffer cache时,尽量避免增加很多的内存而只是提高少量hit ratio的情况出现。
我们还可以查询每种buffer cache的统计信息,主要关注的还是consistent_gets和db_block_gets以及
physical_reads的值。
v$sysstat中名称以DBWR开头的都是有关DBWR后台进程相关的统计信息。当DBWR进程写完脏数据块以后或者扫描完LRU链表以后更新这些统计信息。DBWR会基于被触发的频率以及所处理的内存数据块的数量与总内存数据块的数量的比例,来进行自我调整。我们可以通过这些统计信息得到一些对当前DBWR运行情况的认识。SQL> SELECT name, block_size,physical_reads, db_block_gets,consistent_gets 2 FROM v$buffer_pool_statistics; NAME BLOCK_SIZE PHYSICAL_READS DB_BLOCK_GETS CONSISTENT_GETS -------------------- ---------- -------------- ------------- --------------- DEFAULT 8192 28978 719 77591 DEFAULT 16384 2 80 11
4.3 buffer cache的等待事件
与buffer cache相关的等待事件包括:latch free、buffer busy waits、free buffer waits。曾经发生过的等
待事件可以从v$system_event(一个等待事件对应一行记录)和v$session_event(一个session一个等待事件对应一行记录)中看到。而当前系统正在经历的等待事件可以从v$session_wait看到。
4.3.1 latch free等待
等待事件“latch free”中与buffer cache有关的有两类:cache buffers chains latch和cache buffers lru chain latch。在理解了上面所描述的有关buffer cache的内部管理机制以后,就应该很容易理解这两个latch产生的原因。
对于buffer cache中的每个hash chain链表来说,都会有一个名为cache buffers chains latch的latch来保护对hash chain的并发操作,这种latch通常也叫作hash latch或CBC latch。数据库中会有很多的cache buffers chains latch,每个latch都叫做child cache buffers chains latch。一个child cache buffers chains latch会管理多个hash chain。前面我们知道,hash chain的数量由一个隐藏参数:_db_block_hash_buckets决定。同样也有一个隐藏参数:_db_block_hash_latches来决定有多少个cache buffers chains latch来管理这些hash chain。该参数的缺省值由buffer cache中所含有的内存数据块的多少决定,当内存数据块的数量
•少于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的数量。
在知道了cache buffers chains latch的数量以后,我们只需要用hash chain的数量除以latch的数量以后,就可以算出每个latch管理多少个hash chain了。我们将下面7532除以1024,就可以知道,当前的系统中,每个latch大概对应8个hash chain。SQL> select count(distinct(hladdr)) from x$bh; COUNT(DISTINCT(HLADDR)) ----------------------- 1024 SQL> select count(*) from v$latch_children where name='cache buffers chains'; COUNT(*) ---------- 1024
当数据库在hash chain搜索需要的数据块时,必须先获得cache buffers chains latch。然后在扫描hash chain的过程中会一直持有该latch,直到找到所要的数据块才会释放该latch。当有进程一直在扫描某条hash chain,而其他进程也要扫描相同的hash chain时,其他进程就必须等待类型为cache buffers chains latch的latch free等待事件。SQL> select x.ksppinm, y.ksppstvl, x.ksppdesc 2 from x$ksppi x , x$ksppcv y 3 where x.indx = y.indx 4 and x.ksppinm like '\_%' escape '\' 5 and ksppinm like '%_db_block_hash_buckets%' 6 ; KSPPINM KSPPSTVL KSPPDESC ---------------------- -------- ------------------------------------- _db_block_hash_buckets 7523 Number of database block hash buckets
不够优化的SQL语句是导致cache buffers chains latch的主要原因。如果SQL语句需要访问过多的内存数据块,那么必然会持有latch很长时间。找出逻辑读特别大的sql语句进行调整。v$sqlarea里那些buffer_gets/executions为较大值的SQL语句就是那些需要调整的SQL语句。这种方式不是很有针对性,比较盲目。网上曾经有人提供了一个比较有针对性的、查找这种引起较为严重的cache buffers chains latch的SQL语句的方式,其原理是根据latch的地址,到x$bh中找对应的buffer header,x$bh的hladdr表示该buffer header所对应的latch地址。然后根据buffer header可以找到所对应的表的名称。最后可以到v$sqltext(也可以到stats$sqltext)中找到引用了这些表的SQL语句。我也列在这里。where条件中的rownum<10主要是为了不要返回太多的行,只要能够处理掉前10个latch等待就能有很大改观。
还有一个原因可能会引起cache buffers chains latch,就是热点数据块问题。这是指多个session重复访问一个或多个被同一个child cache buffers chains latch保护的内存数据块。这主要是应用程序的问题。大多数情况下,单纯增加child cache buffers chains latches的个数对提高性能没有作用。这是因为内存数据块是根据数据块地址以及hash chain的个数来进行hash运算从而得到具体的hash chain的,而不是根据child cache buffers chains latches的个数。如果数据块的地址以及hash chain的个数保持一致,那么热点块仍然很有可能会被hash到同一个child cache buffers chains latch上。可以通过v$session_wait的p1raw字段来判断latch free等待事件是否是由于出现了热点块。如果p1raw保持一致,那么说明session在等待同一个latch地址,系统存在热点块。当然也可以通过x$bh的tch来判断是否出现了热点块,该值越高则数据块越热。select /**//*+ rule */ s.sql_text from x$bh a,dba_extents b, (select * from (select addr from v$latch_children where name = 'cache buffers chains' order by sleeps desc) where rownum<11) c, v$sqltext s where a.hladdr = c.addr and a.dbarfil = b.relative_fno and a.dbablk between b.block_id and b.block_id + b.blocks and s.sql_text like '%'||b.segment_name||'%' and b.segment_type='TABLE' order by s.hash_value,s.address,s.piece /
接下来,我们就可以根据p1raw的值去找到所对应的内存数据块以及对应的表的名称了。SQL> select sid, p1raw, p2, p3, seconds_in_wait, wait_time, state 2 from v$session_wait 3 where event = 'latch free' 4 order by p2, p1raw; SID P1RAW P2 P3 SECONDS_IN_WAIT WAIT_TIME STATE ---- -------- --- --- --------------- ---------- ------------------ 38 6666535C 13 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
要解决热点块的问题,可以通过将热点块中的行分散到多个数据块中去,这样原来的热点块就变成了多个数据块,这样被hash到同一个latch的几率就降低了。如果热点块属于表,则可以先将表的数据导出来,然后增加表的pctfree值,最后将数据再导入。如果热点块属于索引,则可以设定较高的 pctfree参数后,重建索引。注意,这会增加索引的高度。select a.hladdr, a.file#, a.dbablk, a.tch, a.obj, b.object_name from x$bh a, dba_objects b where (a.obj = b.object_id or a.obj = b.data_object_id) and a.hladdr = '6666535C';
通过前面我们已经知道,每个working set都会有一个名为cache buffers lru chain的latch(也叫做lru latch)来管理。任何要访问working set的进程都必须先获得cache buffers lru chain latch。cache buffers lru chain latch争用也是由于低效的扫描过多的内存数据块的SQL语句引起的。调整这些语句以降低逻辑读和物理读。只要修改一下上面找引起cache buffers chains latch的SQL语句即可找到这样的SQL语句。
select /**//*+ rule */ s.sql_text from x$bh a,dba_extents b, (select * from (select addr from v$latch_children where name = 'cache buffers lru chain' order by sleeps desc) where rownum<11) c, v$sqltext s where a.hladdr = c.addr and a.dbarfil = b.relative_fno and a.dbablk between b.block_id and b.block_id + b.blocks and s.sql_text like '%'||b.segment_name||'%' and b.segment_type='TABLE' order by s.hash_value,s.address,s.piece /
4.3.2 buffer busy waits等待
当一个session在读取或修改buffer cache里的内存数据块时,首先必须获得cache buffers chains latch,获得以后,到hash chain上遍历直到找到需要的buffer header后。这时,该session必须在该buffer header上以share或exclusive模式(具体哪个模式由该session的操作决定)获得一个buffer lock或一个buffer pin。一旦buffer header被pin住,session就将释放cache buffers chains latch,然后可以在该buffer上进行操作了。如果无法获得buffer pin,那么该session就会等待buffer busy waits等待事件。该等待事件不会出现在session的私有PGA里。
buffer busy waits等待事件不能像latch free等待那样可以相对比较容易的进行事后跟踪。对于该等待事件,oracle提供了v$waitstat视图。v$waitstat里的记录都是buffer busy waits等待事件发生时进行更新的。也就是说,该视图体现的都是buffer busy waits等待事件的统计数据。但这只能给你提供一个大概的buffer busy waits的分布。如果要想具体的诊断该等待事件,只能当发生该等待时,到v$session_wait里去找原因,从而才能找到解决的办法。处理buffer busy wait等待事件时,首先使用下面的SQL语句找到发生等待的数据块类别以及对应的segment。
select 'Segment Header' class, a.segment_type, a.segment_name, a.partition_name from dba_segments a, v$session_wait b where a.header_file = b.p1 and a.header_block = b.p2 and b.event = 'buffer busy waits' union select 'Freelist Groups' class, a.segment_type, a.segment_name, a.partition_name from dba_segments a, v$session_wait b where b.p2 between a.header_block + 1 and (a.header_block + a.freelist_groups) and a.header_file = b.p1 and a.freelist_groups > 1 and b.event = 'buffer busy waits' union select a.segment_type || ' block' class, a.segment_type, a.segment_name, a.partition_name from dba_extents a, v$session_wait b where b.p2 between a.block_id and a.block_id + a.blocks - 1 and a.file_id = b.p1 and b.event = 'buffer busy waits' and not exists (select 1 from dba_segments where header_file = b.p1 and header_block = b.p2);
然后,根据不同的数据块类型进行相应的处理。
1) 如果数据块类型为data block,如果版本为10g之前,则可以同时参照p3列的值来共同诊断。如果p3为130意味着同时有很多session在访问同一个data block,而且该data block没有在内存里,而必须从磁盘上获取。有三种方法可以降低该事件出现的频率:
a、降低并发性。这个比较难实现。
b、找出并优化含有这些segment的SQL语句,以降低物理和逻辑读。
c、增加freelists和freelist groups。
如果没有足够的freelists,当同时对同一个表进行insert时,这就很容易引起buffer busy waits等待。如果正在等待buffer busy waits的session正在进行insert操作,那么需要检查以下那个表有多少freelists了。当然,由于freelists的不足主要会导致对于segment header的buffer busy waits等待。
如果p3为220意味着有多个session同时修改在一个block(该block已经被读入内存了)里的不同的行。这种情况通常出现在高DML并发性的环境里。有三种方法可以降低该事件出现的频率:
a、降低并发性。这个比较难实现。
b、通过增加pctfree减少block里含有的行数。
c、将该对象移到拥有较小block尺寸的表空间里(9i或以上)。
2) 如果数据块类型为data segment header(表或索引的segment header,不是undo segment header)上发生buffer busy waits等待事件,通常表明数据库里有些表或索引的段头具有频繁的活动。
进程访问segment header主要有两种原因:一是获得或修改process freelists信息;二是扩展HWM。有三种方法可以降低该事件出现的频率:
a、增加争用对象的freelists和freelist groups的数量。
b、确定pctfree和pctused之间的间隔不要太小。
c、确保next extent的尺寸不要太小。
d、9i以后,使用ASSM特性来管理block。
3) 如果数据块类型为undo segment headers的争用等待,表明数据库中的rollback segments太少,或者他们的extent size太小,导致对于同一个segment header的大量更新。如果使用了9i以后的auto undo management,则不用处理,因为oracle会根据需要自动创建新的undo segments。如果是9i之前,则可以创建新的private rollback segments,并把它们online,或者通过降低transactions_per_rollback_segment参数来减轻该等待。
4) 如果数据块类型为undo block,说明有多个session同时访问那些被更新过的block。这是应用系统的问题,在数据库来说对此无能为力。
4.3.3 buffer busy waits等待
在一个数据块被读入buffer cache之前,oracle进程必须为该数据块获得一个对应的可用的内存数
据块。当session在LRU list上无法发现一个可用的内存数据块或者搜寻可用的内存数据块被暂停的时候,该session就必须等待free buffer waits事件。
从前面的描述,我们已经知道,一个需要可用内存数据块的前台进程会连续扫描LRU 链表,直到达到一个限定值(也就是隐藏参数_db_block_max_scan_pct所指定的值,表示已经扫描的buffer header数量占整个LRU链表上的buffer header的总数量,在9i中该限定值为40%)。如果到该限定值时还没找到可用内存数据块时,该前台进程就会触发DBWR进程以便清空一些脏数据块,从而使得在辅助LRU链表上能够挂上一些可用的内存数据块。在DBWR进程工作时,该前台进程就必须等待free buffer waits。
oracle跟踪每次对于可用的内存数据块的请求次数(记录在v$sysstat里的free buffer requested),也跟踪每次请求可用的内存数据块失败的次数(记录在v$system_event里的free buffer waits的total_waits)。而v$sysstat里的free buffer inspected则说明oracle为了找到可用的内存数据块所所跳过的数据块的个数,如果buffer cache很空,有很多空的数据块的话,则该值为0。如果free buffer inspected相对free buffer requested来说很高,则说明oracle进程需要扫描更多的LRU链表上的数据块才可以找到可用的数据块。
SQL> select * 2 from v$sysstat 3 where name in ('free buffer requested', 'free buffer inspected'); STATISTIC# NAME CLASS VALUE ---------- ------------------------------ ----------- ---------- 75 free buffer requested 8 290532493 79 free buffer inspected 8 2983596 SQL> select * 2 from v$system_event 3 where event = 'free buffer waits'; EVENT TOTAL_WAITS TOTAL_TIMEOUTS TIME_WAITED AVERAGE_WAIT TIME_WAITED_MICRO ----------------- ----------- -------------- ----------- ------------ ----------------- free buffer waits 1003 476 71075 71 710749256
可以看到,该系统的free buffer waits等待很少,总共等待的时间才0.476秒。同时也可以看到,请求了290532493(free buffer requested)个可用的内存数据块,但是在这个过程中只跳过了2983596(free buffer inspected)个数据块,二者相差2个数量级。说明系统很容易就找到可用的内存数据块。
如果一个session花费了很多的时间等待free buffer waits等待事件的话,通常可能有以下原因:
1) 低效率的SQL语句:对于那些引起很大逻辑读的SQL语句(v$sql里的disk_reads),那些SQL语句可能进行了全表扫描,索引全扫描、或者通过了不正确的索引扫描表等。调整这些SQL语句以降低逻辑读。
2) DBWR进程不够多:也可以通过增加DBWR checkpoints的个数来降低free buffer waits。9i下,可以通过减小fast_start_mttr_target参数来缩短MTTR,从而增加DBWR进程启动的次数。然而,这也有可能引起进程等待write complete waits事件。
3) I/O子系统太慢。
4) 延迟的块清除(block clearouts):通常发生的情形是,晚上向数据库导入了一个很大的表。然后早上运行应用系统时,会发现有有进程在等待buffer busy waits。这是因为第一个访问该表的进程将进行一个延迟的块清除,而这会导致free buffer waits等待事件。解决方法是在导入表完毕以后,执行一句全表扫描,比如通常是:select count(*) from该大表。这样在后面的进程再次访问的时候就不会产生free buffer waits等待事件了。
5) buffer cache太小:遇到free buffer waits事件,首先想到的就是增加buffer cache的大小。