oracle学习篇:五、Buffer Cache与Shared Pool原理

Buffer Cache与Shared Pool是SGA中的最重要和最复杂的两个部分。

5.1 Buffer Cache原理

当一个进程需要访问数据时,首先需要确定数据在内存中是否存在:

如果数据在buffer中存在,则需要根据数据的状态来判断是否可以直接访问还是需要构造一致性读取;

如果数据在buffer中不存在,则需要在buffer cache中寻找足够的空间以装载需要的数据,如果在buffer cache中找不到足够的空间,则需要触发dbwn去写脏数据,释放buffer空间。

5.1.1 LRU与Dirty List

在buffer cache中,oracle通过几个链表进行内存管理,其中最为人熟知的是LRU List和Dirty List。

数据库初始化时,所有的Buffer都被hash 到LRU List上管理,当需要从数据文件上读取数据时,首先要在LRU LIst上寻找Free的Buffer,然后读取数据到Buffer cache中;当数据被修改后,状态变为dirty,就可以被移动至dirty list(checkpoint queue),dirty list上的都是候选的可以被DBWR写出到数据文件的buffer,一个buffer要么在LRU List上,要么在Dirty List上存在,不能同时存在于多个List。

Buffer Cache的原理:

(1)当一个服务器进程需要读取数据到buffer cache中时,首先必须判断该数据在buffer中是否存在,如果存在且可用,则获取该数据,根据LRU算法在LRU List上移动该Block;如果Buffer中不存在该数据,则需要从数据文件上读取。

(2)在读取数据之前,服务器进程需要扫描LRU List寻找Free的buffer,扫描的过程中服务器进程会把发现的所有已经被修改过的buffer移动到checkpoint queue上,这些dirty buffer随后可以被写出到数据文件。

(3)如果checkpoint queue超过了阈值,服务器进程就会通知DBWN去写出脏数据,这也是触发dbwn写的一个条件,这个阈值曾经提到是25%,也就是检查点队列超过25%满就会触发dbwn的写操作。

入股服务器进程扫描LRU超过一个阈值仍然不能找到足够的Free Buffer,将停止寻找,转而通知dbwn去写出脏数据,释放内存空间。

同样这个阈值可以从以上字典表中查询得到,这个数据是40%;

同时由于增量检查点的引入,dbwn也会主动扫描lru list,将发现的dirty buffer移植checkpoint queue,这个扫描也受一个内部约束,这个比例是25%。

(4)找到足够的buffer之后,服务器进程就可以将buffer从数据文件读入buffer cache中。

(5)如果读取的block不满足读一致性需求,则服务器进程需要通过当前block版本和回滚段构造镜像返回给用户。

 LRU List和Dirty List又分别增加了辅助List(AUXILIARY List),用于提高管理效率。引入了辅助List之后,当数据库初始化时,Buffer首先存放在LRU的辅助list上,当被使用后移动到LRU主List上,这样当用户进程所有free buffer时,就可以从LRU-AUX List开始,而DBWN搜索Dirty Buffer时,则可以从LRU-Main List开始,从而提高了所有效率和数据库性能。

可以通过如下命令转储Buffer Cache的内容,从而清晰地看到以上描述的数据结构。

alter session set event 'immediate trace name buffers level 4';

不同level转储的内容详细程度不同,此命令的可用级别主要有1~10级,各级别含义各不相同。

5.1.2 Cache Buffers Lru Chain闩锁竞争与解决问题

当用户进程需要读取数据到buffer cache时,或cache buffer根据LRU算法进行管理时,就不可避免地要扫描LRU List获取可用buffer或更改buffer状态,我们知道,oracle的buffer cache是共享内存,可以为众多并发进程并发访问,所以在搜索的过程中必须获取Latch(Latch是oracle的一种串行锁机制,用于保护共享内存结构),锁定内存结构,防止并发访问损坏内存中的数据。

这个用于锁定LRU的Latch就是经常见到的Cache Buffers Lru Chain。

Cache Buffers Lru Chain Latch存在多个子Latch,其数量受隐含参数_db_block_lru_latches控制,可以通过v$latch_children视图查看当前各子Latch使用情况。

select * from v$latch_children where name='cache buffers lru chain';

如果该latch竞争激烈,通常有如下方法可以采用:

(1)适当增大buffer cache,这样可以减少读数据到buffer cache的机会,减少扫描LRU List的竞争;

(2)可以适当增加LRU Latch的数量,修改_db_block_lru_latches参数可以实现,但是该参数通常来说是足够的,除非在oracle support的建议下或通知该参数将带来的影响,否则不推荐修改;

(3)通过多缓冲池技术,可以减少不希望的数据老化和全表扫描等操作对default池的冲击,从而可以减少竞争。

5.1.3 Cache Buffer Chain闩锁竞争与解决

在LRU和Dirty List这两个内存结构之外,Buffer Cache的管理还存在另外两个重要的数据结构:Hash Bucket和Cache Buffer Chain。

(1)Hash Bucket和Cache Buffer Chain

可以想象,如果所有的Buffer Cache中的所有Buffer都通过同一个结构管理,当需要确定某个Block在buffer中buffer中是否存在时,将需要遍历会整个结构,性能会相当低下。

为了提高效率,oracle引入了Bucket的数据结构,oracle把管理的所有Buffer通过一个内部的Hash算法运算后,存放到不同的Hash Bucket中,这样通过Hash Bucket进行分割后,众多的Buffer被分布到一定数量的Bucket之中,当用户需要在Buffer中定位数据是否存在时,只需要通过同样的算法活得Hash值,然后到响应的Bucket中查找少量的Buffer即可确定。每个buffer存放的bucket由buffer的数据块地址(DBA,Data Block Address)运算决定。

Bucket内部,通过cache Buffer Chain(Cache Buffer Chain是一个双向链表)将所有的Buffer通过Buffer Header信息联系起来。

Buffer Header存放的是对应数据块的概要信息,包括数据块的文件号、块地址、状态等。要判断数据块在Buffer中是否存在,通过检查Buffer Header即可确定。

图书馆就是Buffer Cache,每个抽屉都是一个Bucket(Hash 运算),抽屉中的每张卡片(分类方式)就是Buffer Header,这些卡片通过一个铁闩串接起来,这就是Cache Buffer Chain。

每个抽屉只有一根铁闩,如果很多读者都想翻阅这个链上的卡片,那么就产生了Cache Buffer Chain的竞争,先来到的那个读者持有了Latch就能不停的翻阅,其他读者只好不停了来检查,如果检查次数多了(超过了_spin_count),也可以去休息一会而,再来和其他读者竞争。

由于Buffer根据Buffer Header进行散列,从而最终决定存入哪一个Hash Bucket,那么Hash Bucket的数量在一定程度上就决定了每个Bucket中BUffer数量的多少,也就间接影响了搜索的性能。

Hash Bucket的设置受一个隐含参数_db_block_hash_buckets的影响。

每个Latch需要管理多个Bucket,但是由于Bucket数量的多倍增加,每个Bucket上的Block数量得意减少,从而使少量Latch管理更多Bucket成为可能。

(2)X$BH与Buffer Header

Buffer Header数据,可以从数据库的字典表中查询得到,这张字典表是X$BH。X$BH中的BH就是指Buffer Headers,每个Buffer在X$BH中都存在一条记录。

select count(*) from x$bh;

show parameter db_block_buffers;

Buffer header中存储每个Buffer容纳的数据块的文件号、块地址、状态等重要信息,根据这些信息,接合dba_extents视图,可以很容易地找到每个Buffer对应的对象信息。

X$BH中还有一个重要字段TCH,TCH为touch的缩写,表示一个Buffer的访问次数,Buffer被访问的次数越多,说明该Buufer越“抢手”,也就可能存在热点快竞争的问题。

查询参考:查询当前数据库最繁忙的Buffer

select * from (select addr,ts#,dbarfil,dbablk,tch from x$bh order by tch desc) where rownum<11;

再结合dba_extents中的信息,可以查询得到这些热点Buffer都来自哪些对象。

select e.owner,e.segment,e.segment_type from dba_extents e ,(上面的语句) b where e.relative_fno=b.dbarfil and e.block_id<=b.dbablk and e.block_id+e.blocks>b.dbablk;

除了查询X$BH之外,也可以从Buffer cache的转储信息中,看到Buffer Header的具体内容,以下信息来自level1级的Buffer Dump:

在oracle 10g之前,数据库的等待事件中,所有Latch等待被归入Latch Free等待事件中,在statspack的report中,如果在top5等待事件中看到Latch Free这一等待处于较高位置,就需要我们介入进行研究和解决。

(3)热点块竞争与解决

Top5等待事件都值得关注,Latch Free是最严重的竞争。

由于Latch Free是一个汇总等待信息,我们需要从v$latch视图获得具体的Latch竞争主要是由哪些Latch引起的。在Statspack report中同样存在这样一部分数据。

注意到Cache Buffer Chains正是主要的Latch竞争。实际上,这个数据库在繁忙的时段,基本上处于停顿状态,大量进程等待Latch Free竞争,这些获得session的等待事件可以很容易地从v$session_wait视图中查询得到。

如果需要具体确定热点对象,可以从v$latch_children中查询具体的子Latch信息。

X$BH中还存在另外一个关键字段HLADDR,Hash Chain Latch Address,这个字段可以和v$latch_child.addr进行关联,这样就可以把具体的Latch竞争和数据块关联起来,再结合dba_extents视图,就可以找到具体的热点竞争对象,找到具体热点竞争对象之后,可以结合v$sqlarea或者v$sqltext,找到频繁操作这些对象的SQL,然后对其进行优化,即可缓解或剞劂热点块竞争的问题。

找到这些SQL之后,可以通过优化SQL较少数据的访问,避免或优化某些容易引起争用的操作(如Connect By等操作)来减少热点块竞争。

5.2 Shared Pool的基本原理

5.2.1 Shared Pool的设置说明

转储Shared Pool共享内存的内容:

alter session set events 'immediate trace name headdump level 2';

5.2.2 了解X$KSMSP视图

Shared Pool的空间分配和使用,可以铜鼓该视图查询。

5.2.3 诊断和解决ORA-04031错误

Shared Pool的根本问题只有一个,就是碎片过多带来的性能问题。

(1)什么是ora-04031错误

当尝试在共享池分配大块的连续内存失败(很多时候是由于碎片过多,而非真是内存不足)时,oracle首先清除共享池中当前没使用的所有对象,使空闲内存块合并。如果仍然没有足够大的单块内存可以满足需要,就会产生ORA-04031错误。

(2)内存泄漏

在oracle9iR2之前的很多ora-04031的错误都和内存泄漏的bug有关,所以是否及时应用相关的patch是非常重要的。

(3)绑定变量和cursor_sharing

如果shared_pool_size设置的足够大,又可以排除Bug的因素,那么大多数的ORA-04031错误都是由共享池中的大量SQL代码等导致了过多的内存碎片而引起的,可能的原因有:

SQL没有足够的共享;

大量不必要的解析调用;

没有使用绑定变量。

使用绑定变量可以充分降低Shared Pool和Library Cache的Latch竞争,从而提高性能。

如果用户的应用没有很好的使用绑定变量,那么oracle提供了一个新的初始化参数用以在server段进行强制变量绑定,这个参数是cursor_sharing。

最初这个参数有两个可选设置:exact和force。缺省的是exact,表示精确匹配;force表示在server端执行强制绑定。后面,第三个选项:similar。该参数指定oracle在存在柱状图信息时,对于不同的变量值重新解析,从而可以利用柱状图更为精确地指定SQL执行计划。即当存在柱状图信息时,similar的表现和exact相同,不存在时与force相同。

(4)使用Flush Shared Pool缓解共享池问题

如果不能修改应用或不能强制变量绑定,oracle还可以提供一种应急处理方法,强制刷新共享池。

alter system flush shared_pool;

刷新共享池可以帮助合并碎片,强制老化SQL,释放共享池,但是这通常是不推荐的做法,因为:

① flush shared pool会导致当前未使用的cursor被清除共享池,如果这些sql随后需要执行,那么数据库将经历大量的硬解析,系统将会经历严重的cpu争用,数据库将会产生激烈的latch竞争。

② 如果应用没有使用绑定变量,大量类似的sql不停执行,那么flush shared pool可能之恶能带来短暂的改善,数据库很快就会回到原来的状态。

③ 如果shared pool很大,并且系统非常繁忙,刷新共享池可能会导致系统挂起,对于类似系统尽量在系统空闲时进行。

oracle数据库的共享池算法发生了改变,刷新共享池不再推荐使用。

(5)shared_pool_reserved_size参数的设置及作用

该参数指定了保留的共享池空间,用于满足大的连续的共享池空间请求。

当共享池出现过多碎片,请求大块空间会导致oracle大范围地查找并释放共享内存来满足请求,由此可能会带来较为严重的性能下降,设置合适的该参数,结合shared_pool_reserved_min_alloc参数可以避免由此导致的性能下降。

这个参数的缺省值是共享池的5%,建议值是10%到20%,大小不得超过50%。

(6)其他

此外,某些特定的SQL,较大的指针或者大的Package都可能导致ora-04031错误。这种情况下,可以考虑把这个大的对象pin到共享池中,减少其动态请求、分配带来的负担。

(7)模拟ora-04031错误

5.2.4 Library Cache Pin及Library Cache Lock分析

oracle使用两种数据结构来进行shared pool的并发访问控制:lock和pin。lock比pin具有更高的级别。

lock在handle上获得,在pin一个对象之前,必须首先获得该handle的锁定。锁定主要有三种模式:null、share和exclusive。在获取访问对象时,通常需要获取空null模式以及share共享模式的锁定。在修改对象时,需要获得exclusive排他锁定。

在锁定了Library Cache对象以后,一个进程在访问之前必须pin该对象。同样pin有三种模式:null、shared和exclusive。指定模式时获得共享pin,修改模式获得排他pin。

通常访问、执行过程和package时,获得的都是共享pin,如果排他pin被持有,那么数据库此时就要产生等待。

(1)Library Cache Pin等待事件

可以认为pin是一种特定形式的锁,出现该等待事件时,通常说明该pin被其他用户以非兼容模式持有。

该等待事件为3秒,其中有1秒用于PMON后台进程,即在取得pin之前最多等待3秒,否则就超时。

Library Cache Pin通常是发生在编译或重新编译对象时,编译通常都是显性的,如安装应用程序、升级、安装补丁包程序等,另外,alter、grant和revoke等操作也会使得object变得无效,可以通过LAST_DDL_TIME观察这些变化。

当object变的无效时,oracle会在第一次访问次object时视图去重新编译它,如果此时其他session已经把此object pin到library cache中,就会出现问题,特别是有大量的活动session并且存在较复杂的dependence时。在某些情况下,重新编译object可能会花几个小时事件,从而阻塞其他视图访问此object的进程。

实际上recompile过程包含以下步骤,同时来看一下lock和pin是如何交替发挥作用的。

存储过程的Library Cache Object以排他模式被锁定,这个锁定是在handle上获得的;

以Shared模式pin该对象,以执行安全和错误检查;

共享pin被释放,重新以排他模式pin该对象,执行重编译;

使所有依赖该过程的对象失效;

释放Exclusive Lock和Exclusive Pin。

(2)Library Cache Lock等待事件

如果此时再发出一条grant或compile的命令,那么Library Cache Lock等待事件将会出现。

alter procedure pining compile;

select * from v$session_wait;

由于handle上的lock已经被session2以exclusive模式持有,所以session3产生了等待。

5.2.5 诊断案例一:version_count过高造成的Latch竞争解决

dump内存:

alter session set events 'immediate trace name LIBRARY_CACHE level 4';

5.2.6 诊断案例二:临时表引发的竞争

问:数据库中几个存储过程总是跑不完,数据库没有锁,空间也足够,请问会是什么原因?

答:检查v$session_wait,看系统在等待什么

可以看到数据库目前正在经历Library Cache Pin和Library Cache Lock的等待和竞争。持有pin的用户在执行truncate table 的操作。

问:这个truncate是嵌在过程里面的?

答:是的,在一个loop中间的,每半个小时调用一次,类似的程序很多,共用表iptt_pm_all。

获取Shared Pool的转储文件用于分析,Level32

alter session set events 'immediate trace name LIBRARY_CACHE level 32';

最终发现是由于truncate临时表不适当地请求了排他锁所致,bug?

5.2.7 小结

第五章完

猜你喜欢

转载自www.cnblogs.com/myheart-new/p/11763343.html