Buffer Cache与Shared Pool原理

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 开始,从而提高了搜索效率和数据库性能。

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

Alter session set events ‘immediate trace name buffers level4’;

不同 Level 转储内容的详细程度不同,此命令的可用级别主要有 1~10 级,其中各级别的含义如下:

Level 1 :仅包含 Buffer Headers 信息;

Level 2 :包含 Buffer Headers Buffer 概要信息转储;

Level 3 :包含 Buffer Headers 和完整 Buffer 内容转储;

Level 4 Level 1+Latch 转储 +LRU 队列;

Level 5 Level 4+Buffer 概要信息转储;

Level 6 Level 7 Level 4+ 完整的 Buffer 内容转储;

Level 8 Level 4 + 显示 users/waiters 信息;

Level 9 Level 5 + 显示 users/waiters 信息;

Level 10 Level 6 + 显示 users/waiters 信息。

 

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 越“抢手”,也就可能存在热点块竞争的问题。

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

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

0430A 6E8          0          1          1       1498        652

0430C 9FC          0          1          1       7882        142

0430A 5A 4          0          1          1       7938        142

0430C 9FC          0          1           1       8202        142

0430C 9FC          0          1          1       7873        107

0430C 9FC          0          1          1       7889        107

0430C 9FC          0          1          1       8233        107

0430C 9FC          5          5           5      18444        106

0430C 9FC          5          5          5      18516        106

0430C 9FC          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

 

转储 Buffer 内容获取跟踪文件:

Alter session set events ‘immediate trace name buffers level10’;

 

Shared Pool 的基本原理

Oracle 通过 Shared Pool 来实现 SQL 共享、减少代码 硬解析等,从而提高数据库的性能。

 

通过如下命令转储 Shared Pool 共享内存的内容:

Alter session set events ‘immediate trace name heapdump level2’;

 

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 没有足够的共享;

大量不必要的解析调用;

没有使用绑定变量。

 

使用 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 的方法已经不再推荐使用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值