通常数据访问和修改需要通过Buffer Cache来完成,当一个进程访问数据时,首先需要确认数据在内存中是否存在(这也可以解释为,为什么当实例发生故障时,登录SQLPLUS会报错。)。如果数据在Buffer中存在,则需要根据数据状态来判断是否可以直接访问还是需要构造一致性读取;如果数据在Buffer中不存在,则需要Buffer Cache中寻找足够的空间以装载需要的数据,如果Buffer Cache中的空间不足,则需要触发DBWR去写出Dirty Data来释放Buffer。

 

LRU与Dirty List

 

Oracle通过几个链表进行内存管理,其中LRU List和Dirty List(也被称为Write List)最常被提及。

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与Shared Pool原理_Buffer Cache

(1) Server进程读数据到Buffer Cache中时,先要判断数据在Buffer中是否存在(过程①),如果存在且可用,则获取该数据,并根据LRU算法在LRU List上移动该Block;如果不存在,则需要的数据从数据文件上读取(过程④)。

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

(3) 如果Checkpoint Queue超过了阀值,Server进程会通知DBWR写出数据(过程③);该过程是触发DBWR写的条件之一。阀值25%是数据库内部定义的:

SQL> set line 300
SQL> col kvittag for a15
SQL> col kvitdsc for a50
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超过一个阀值(Oracle内部定义为40%)扔不能找到足够的Free Buffer,将停止寻找。转而通知DBWR去写出脏数据,释放内存空间。

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

同时由于检查点的引入,DBWR也会主动扫描LRU List,将发现的Dirty Buffer移至Checkpoint Queue,该扫描也受一个内部约束。11G中已取消该约束?

SQL> select kvittag,kvitval,kvitdsc from x$kvit where kvittag='kcbdsp';
no rows selected

(4) 找到足够的Buffer后,Server进程可以将数据文件中的数据读入Buffer Cache(图④所示)。

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

 

随着Oracle版本的升级,Oracle后续又引入了Auxiliary List,用于提高管理效率。引入该列表之后,当数据库初始化时,Buffer首先存放在LRU的Auxiliary List上(Auxiliary RPL_LST),当被私用后移动到LRU主List上(Main RPL_LST),这样用户进程扫描Free Buffer时,可以从LRU-AUX List开始。而DBWR扫描则可以再LRU-MAIN List上进行。从而提高扫描效率和数据库性能。

 

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

SQL> alter session set events 'immediate trace name buffers level 4';

Level 1:仅包含Buffer Headers 信息。

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

Level 3:包含Buffer Headers和完整Buffer内存转储。

Level 4:Level1+Latch转储+LRU队列。

Level 5:Level4+Buffer概要信息转储。

Level 6/7:Level4+完整Buffer内存转储。

Level 8:Level4+显示users/waits信息。

Level 9:Level 5+显示users/waits信息。

Level 10:Level 6+显示users/waits信息。

 

因为生产环节中转储文件可能非常大,建议设置初始化参数max_dump_file_size为UNLIMITED。

DUMP文件所在位置,可以通过V$DIAG_INFO获得,DUMP中的具体内容这里在详细介绍。