BUFFER CACHE和SHARED POOL原理

http://blog.csdn.net/wanghai__/article/details/4881492

Buffer Cache

LRU与Dirty List

在Buffer Cache中,Oracle通过几个链表进行内存管理。

LRU list用于维护内存中的Buffer,按照LRU算法进行管理。数据库初始化时,所有的Buffer都被Hash到LRU list上管理。当需要从数据文件上读取数据时,首先要在LRU List上寻找Free的Buffer,然后读取数据到Buffer Cache中;当数据被修改之后,状态变为Dirty,就可以被移动至Dirty List,Dirty List上的都是候选的可以被DBWR写出到数据文件的Buffer,一个Buffer要么在LRU List上,要么在Dirty List上存在,不能同时存在于多个list。

Buffer Cache的原理及使用

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

②在读取数据之前,Server进程需要扫描LRU List寻找Free的Buffer,扫描过程中Server进程会把发现的所有已经被修改过的Buffer移动到Checkpoint Queue上,这些Dirty Buffer随后可以被写出到数据文件。

③如果Checkpoint Queue超过了阈值,Server进程就会通知DBWn去写出脏数据;这也是触发DBWn写的一个条件,这个阈值曾经提到是25%,也就是当检查点队列超过25%满就会触发DBWn的写操作:

SQL> Select kvittag,kvitval,kvitdsc from x$kvit where kvittag='kcbldq';

KVITTAG           KVITVAL KVITDSC

--------------- ---------- --------------------------------------------------

kcbldq                 25 large dirty queue if kcbclw reaches this

④如果Server进程扫描LRU超过一个阈值仍然不能找到足够的Free Buffer,将停止寻找,转而通知DBWn去写出脏数据,释放内存空间。这个数字是40%,也就是说当Server进程扫描LRU超过40%还没能找到足够的Free Buffer就会停止搜索,通知DBWn执行写出,这时进程会处于free buffer wait等待:

SQL> Select kvittag,kvitval,kvitdsc from x$kvit where kvittag='kcbfsp';

KVITTAG      KVITVAL KVITDSC

---------- ---------- -------------------------------------------------------

kcbfsp            40 Max percentage of LRU list foreground can scan for free

⑤同时由于增量检查点的引入,DBWn也会主动扫描LRU List,将发现的Dirty Buffer移至Checkpoint Queue,这个扫描也受到一个内部约束,在oracle 9iR2中这个比例是25%:

SQL> Select kvittag,kvitval,kvitdsc from x$kvit where kvittag='kcbdsp';

KVITTAG      KVITVAL KVITDSC

---------- ---------- -------------------------------------------------------

kcbdsp            25 Max percentage of LRU list dbwriter can scan for dirty

 

找到足够的Buffer之后,Server进程就可以将Buffer从数据文件读入Buffer Cache。

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

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

 

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:

SQL> select addr,latch#,name,gets,misses,immediate_gets,immediate_misses

 2 from v$latch where name='cache buffers lru chain';

ADDR        LATCH# NAME                           GETS    MISSES

-------- ---------- ------------------------- ---------- ----------

IMMEDIATE_GETS IMMEDIATE_MISSES

-------------- ----------------

01FED330        92 cache buffers lru chain       12409         3

         5849               0

 

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

SQL> select addr,child#,name,gets,misses,immediate_gets,immediate_misses

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

 

ADDR        CHILD# NAME                           GETS    MISSES

-------- ---------- ------------------------- ---------- ----------

IMMEDIATE_GETS IMMEDIATE_MISSES

-------------- ----------------

67EA55E8         8 cache buffers lru chain          22         0

            0               0

 

67EA511C         7 cache buffers lru chain          22         0

            0               0

 

67EA4C50         6 cache buffers lru chain          22         0

            0               0

 

67EA4784         5 cache buffers lru chain          22         0

            0               0

 

67EA42B8         4 cache buffers lru chain          22         0

            0               0

 

67EA3DEC         3 cache buffers lru chain       12553         3

         6137               0

 

67EA3920         2 cache buffers lru chain          22         0

            0               0

 

67EA3454         1 cache buffers lru chain          22         0

            0               0

 

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

  • 适当增大Buffer Cache,这样可以减少读数据到Buffer Cache的机会,减少扫描LRU List的竞争;
  • 可以适当增加LRU Latch数量,修改_db_block_lru_latches参数,该方法不推荐使用;
  • 通过多缓冲池技术,可以减少不希望的数据老化和全表扫描等操作对Default池的冲击,从而可以减少竞争。

 

Cache Buffer Chain闩锁竞争与解决

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

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

为了提高效率,Oracle引入了Bucket的数据结构,Oracle把管理的所有Buffer通过一个内部的Hash算法运算后,存放到不同的Hash Bucket中,这样通过Hash Bucket分割之后,众多的Buffer被分布到一定数量的Bucket之中,当用户需要在Buffer中定位数据是否存在时,只需要通过同样的算法获得Hash值,然后到相应得Bucket中查找少量的Buffer即可确定。

Bucket内部通过Cache Buffer Chain(Cache Buffer Chain是一个双向链表)将所有的Buffer通过Buffer Header信息联系起来。Buffer Header存放的是对应数据块的概要信息,包括数据块的文件号、块地址、状态等。要判断数据块在Buffer中是否存在,检查Buffer Header即可确定。

对应每个Bucket,只存在一个Chain,当用户试图搜索Cache Buffer Chain时,必须首先获得Cache Buffer Chain Latch。

总结:

 

 

 

①从Oracle 8i开始,Bucket的数量比以前大大增加;通过增加的Bucket的“稀释”使得每个Bucket上的Buffer数量大大减少。

②在Oracle 8i之前,_db_block_hash_latches的数量和Bucket的数量是一致的,每个Latch管理一个Bucket;从Oracle 8i开始每个Latch需要管理多个Bucket,由于每个Bucket上的Buffer数量大大降低,所以Latch的性能反而得到提高。

③每个Bucket存在一条Cache Buffer Chain。

④Buffer Header上存在指向具体Buffer的指针。

 

X$BH与Buffer Header

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

 

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

 

可以通过以下查询获得当前数据库最繁忙的Buffer

SQL> select *

 2 from (select addr,ts#,file#,dbarfil,dbablk,tch

 3 from x$bh order by tch desc)

 4 where rownum<11;

 

ADDR           TS#     FILE#   DBARFIL    DBABLK       TCH

-------- ---------- ---------- ---------- ---------- ----------

0430A6E8         0         1         1      1498       652

0430C9FC         0         1         1      7882       142

0430A5A4         0         1         1      7938       142

0430C9FC         0         1         1      8202       142

0430C9FC         0         1         1      7873       107

0430C9FC         0         1         1      7889       107

0430C9FC         0         1         1      8233       107

0430C9FC         5         5         5     18444       106

0430C9FC         5         5         5     18516       106

0430C9FC         0         1         1      9017       106

 

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

SQL> select e.owner,e.segment_name,e.segment_type

 2 from dba_extents e,

 3 (select *

 4 from (select addr,ts#,file#,dbarfil,dbablk,tch

 5 from x$bh order by tch desc)

 6 where rownum<11) b

 7 where e.relative_fno=b.dbarfil

 8 and e.block_id<=b.dbablk

 9 and e.block_id+e.blocks>b.dbablk;

OWNER          SEGMENT_NAME             SEGMENT_TYPE

--------------- ------------------------- -------------------------

SYS            I_JOB_NEXT               INDEX

SYS            I_FILE#_BLOCK#           INDEX

SYS            C_FILE#_BLOCK#           CLUSTER

SYS            I_FILE#_BLOCK#           INDEX

SYS            C_USER#                  CLUSTER

SYS            I_OBJ1                   INDEX

SYS            OBJ$                     TABLE

SYS            OBJ$                     TABLE

SYS            C_FILE#_BLOCK#           CLUSTER

SYS            C_TS#                    CLUSTER

 

Shared Pool

Shared Pool通过Free Lists管理Free内存块(Chunk),Free的内存块(Chunk)按不同size被划分到不同的部分(Bucket)进行管理。

初始时,数据库启动以后,Shared Pool多数是连续内存块,但是当空间分配使用以后,内存块开始被分割,碎片开始出现,Bucket列表开始变长。Oracle请求Shared Pool空间时,首先进入相应的Bucket进行查找。如果找不到,则转向下一个非空的Bucket,获取第一个Chunk。如果找不到,则转向下一个非空的Bucket,获取第一个Chunk。分割这个Chunk,剩余部分会进入相应的Bucket,进一步增加碎片。

最终的结果是,由于不停分割,每个Bucket上的内存块会越来越多,越来越碎小。碎片过多会导致搜索Free List的时间过长,而Free Lists的管理和搜索都需要获得和持有一个非常重要的Latch,就是Shared Pool Latch。Latch是Oracle数据库内部提供的一种低级锁,通过串行机制保护共享内存不被并发更新/修改所损坏。Latch的持有通常都非常短暂,但是对于一个繁忙的数据库,这个串行机制往往会成为极大的性能瓶颈。

如果Free Lists链表过长,搜索这个Free Lists的时间就会变长,从而导致Shared Pool Latch被长时间持有,在一个繁忙的系统中,这会引起严重的Shared Pool Latch竞争。

在Oracle 9i中,为了增加对于大共享池的支持,Shared Pool Latch从原来的一个增加到现在的7个。

SQL> select addr,name,gets,misses,spin_gets

 2 from v$latch_children where name='shared pool';

ADDR    NAME                 GETS    MISSES SPIN_GETS

-------- --------------- ---------- ---------- ----------

031D3724 shared pool             0         0         0

031D365Cshared pool             0         0         0

031D3594 shared pool             0         0         0

031D34CC shared pool             0         0         0

031D3404 shared pool             0         0         0

031D333Cshared pool             0         0         0

031D3274 shared pool         90710         1         0

 

 

判断和解决ORA-04031错误

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

 

绑定变量和cursor_sharing

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

SQL没有足够的共享;

大量不必要的解析调用;

没有使用绑定变量。

CURSOR_SHARING

 

Property

Description

Parameter type

String

Syntax

CURSOR_SHARING = { SIMILAR | EXACT | FORCE }

Default value

EXACT

Modifiable

ALTER SESSION, ALTER SYSTEM

Basic

No

 

 

CURSOR_SHARING determines what kind of SQL statements can share the same cursors.

Values:

  • FORCE

Forces statements that may differ in some literals, but are otherwise identical, to share a cursor, unless the literals affect the meaning of the statement.

  • SIMILAR

Causes statements that may differ in some literals, but are otherwise identical, to share a cursor, unless the literals affect either the meaning of the statement or the degree to which the plan is optimized.

  • EXACT

Only allows statements with identical text to share the same cursor.

Note:

Forcing cursor sharing among similar (but not identical) statements can have unexpected results in some DSS applications, or applications that use stored outlines.

 

 

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

Alter system flush shared_pool;

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

  • Flush Shared Pool会导致当前未使用的cursor被清除出共享池,如果这些SQL随后需要执行,那么数据库将经历大量的硬解析,系统将会经历严重的CPU争用,数据库将会产生严重的latch竞争。
  • 如果应用没有使用绑定变量,大量类似的SQL不停执行,那么Flush Shared Pool可能只能带来短暂的改善,数据库很快就会回到原来的状态。
  • 如果Shared Pool很大,并且系统非常繁忙,刷新Shared Pool可能会导致系统挂起,对于类似系统尽量在系统空闲时进行。
  • 从Oracle 9i开始,Oracle的共享池算法发生了改变,Flush Shared Pool的方法已经不再推荐使用。

 

SHARED_POOL_RESERVED_SIZE参数的设置及作用

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

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

这个参数理想值应该大到足以满足任何对RESERVED LIST的内存请求,而无需数据库从共享池中刷新对象。这个参数的缺省值是shared_pool_size的5%,通常这个参数的建议值为shared_pool_size参数的10%~20%大小,最大不得超过shared_pool_size的50%。

shared_pool_reserved_min_alloc这个参数的值控制保留内存的使用和分配。如果在共享池空闲列表中请求一个足够尺寸的大块内存,但没有找到合适的空间,内存就从保留列表(RESERVED LIST)中分配一块比这个参数值大的空间。

SHARED_POOL_RESERVED_SIZE

 

Property

Description

Parameter type

Big integer

Syntax

SHARED_POOL_RESERVED_SIZE = integer [K | M | G]

Default value

5% of the value of SHARED_POOL_SIZE

Modifiable

No

Range of values

Minimum: 5000

Maximum: one half of the value of SHARED_POOL_SIZE

 

 

SHARED_POOL_RESERVED_SIZE specifies (in bytes) the shared pool space that is reserved for large contiguous requests for shared pool memory.

You can use this parameter to avoid performance degradation in the shared pool in situations where pool fragmentation forces Oracle to search for and free chunks of unused pool to satisfy the current request.

SHARED_POOL_RESERVED_MIN_ALLOC - The value of this parameter controls allocation of reserved memory. Memory allocation larger than this value can allocate space from the reserved list if a chunk of memory of sufficient size is not found on the shared pool free lists. The default value is adequate for most systems. If you increase the value, then the Oracle server will allow fewer allocations from the reserved list and will request more memory from the shared pool list. This parameter is hidden in Oracle 8i and higer, but it can be found by executing the following SQL statement: 
select nam.ksppinm NAME,
val.KSPPSTVL VALUE 
from x$ksppi nam,
x$ksppsv val
where nam.indx = val.indx
and nam.ksppinm like '%shared%'
order by 1;

 

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。

 

下面引用一段其他网友对LOCK和PIN这两个概念的理解

library cache object在内存中是分成2部分来存储的,第一部分是头信息,叫做handle,头信息里保存了指向具体保存该对象的地址,这个叫做heap.一个对象可能分为很多个heap要访问或者修改一个对象,首先要锁住其头信息,也就是handle,以防止其他session同时访问或者修改该对象,这是library cache lock要做的事情而当实际的访问或者修改对象的内容时,则要根据头信息去访问或者修改具体的heap的,那么也要防止其他session同时访问或者修改这些heap,所以对heap需要加library cache pin来保护

posted on 2017-07-30 12:22  HelonTian  阅读(478)  评论(0编辑  收藏  举报