首页 > 数据库 > Oracle > Buffer cache原理
在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竞争激烈,通常有如下方法可以采用:
在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 * from (select addr,ts#,file#,dbarfil,dbablk,tch from x$bh order by tch desc) 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
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/25725504/viewspace-1357904/ ,如需转载,请注明出处,否则将追究法律责任。