Buffer Cache
Buffer Cache是SGA区中专门用于存放从数据文件中读取的的数据块拷贝的区域。Oracle进程如果发现需要访问的数据块已经在buffer cache中,就直接读写内存中的相应区域,而无需读取数据文件,从而大大提高性能(内存的读取效率是磁盘读取效率的14000倍)。Buffer cache对于所有oracle进程都是共享的,即能被所有oracle进程访问。
一、CBC、Hash Bucket与Hash Chain List
CBC链,用来查找block是否在buffer cache中的时候,需要用到,用以快速定位Buffer。Oracle将buffer cache中所有的buffer通过一个内部的Hash算法运算之后,将这些buffer放到不同的 Hash Bucket中。每一个Hash Bucket中都有一个
Hash Chain List,通过这个list,将这个Bucket中的block串联起来,Hash Chain List是一个双向链表。
从图中我们可以看到,一个latch:cache buffers chains(CBC) 可以保护多个Hash Bucket,也就是说,如果我要访问某个block,我首先要获得这个latch,一个Hash Bucket对应一个Hash Chain List,而这个Hash Chain List挂载了一个或者多个Buffer Header。
二、Oracle对数据块的访问过程
1、对该Block运用Hash算法,得到Hash值。
2、获得cache buffers chains latch
3、到相应的Hash Bucket中搜寻相应Buffer Header
4、如果找到相应的Buffer Header,然后判断该Buffer的状态,看是否需要构造CR Block,或者Buffer处于pin的状态,最后读取。
5、如果找不到,就从磁盘读入到Buffer Cache中。(物理读)
latch:cache buffers chains的等待事件:
在Oracle9i以前,如果其它用户进程已经获得了这个latch,那么新的进程就必须等待,直到该用户进程搜索完毕(搜索完毕之后就会释放该latch)。从Oracle9i开始 cache buffers chains latch可以只读共享,也就是说用户进程A以只读(select)的方式访问Block,这个时候获得了该latch,同时用户进程B也以只读的方式访问Block,那么这个时候由于是只读的访问,用户进程B也可以获得该latch。但是,如果用户进程B要以独占的方式访问Block,那么用户进程B就会等待用户进程A释放该latch,这个时候Oracle就会对用户进程B标记一个latch:cache buffers chains的等待事件。
latch:cache buffers chains解决:
1、优化SQL。大量逻辑读的SQL语句就有可能产生非常严重的latch:cache buffers chains等待,因为每次要访问一个block,就需要获得该latch,由于有大量的逻辑读,那么就增加了latch:cache buffers chains争用的机率。
2、Hash Bucket太少,需要更改_db_block_hash_buckets隐含参数。9i之后就基本不会出现这个问题了。
三、LRU List
1、LRU的引入
数据从DBF文件中加载到buffer cache的过程中,首先会使用Free的Buffer 来缓存DBF的Block。当数据库使用一段时间后,Buffer Cache中将不存在Free的Buffer,这个时候,应该使用什么样的Buffer来缓存数据块呢?
我们介绍下Buffer的几种状态。当buffer没有被使用的时候,状态是Free的,一旦缓存数据,状态立马变成clean的。如果这个buffer的数据被更改了,但是没有被提交,这个时候,状态就是dirty的。一旦提交,就会重新变成clean的。回到上面的问题,Free的Buffer使用完了,最合理的,应该是使用什么样的buffer呢?Oracle给我们的答案是:使用“最近使用最少的”clean块,做为替换对象!Oracle数据库的buffer cache是很大的,如果一个个去比较,寻找,效率是极低的。所以,oracle引入LRU的概念。
LRU是一个双向链表,LRU链表的两端就分别叫做最近使用端(the Most Recently Used MRU)和最近最少使用端(LRU)。如果一个用户进程发现某个block不在Buffer Cache中,那么用户进程就会从磁盘上将这个block读入Buffer Cache。在将block读入到Buffer Cache之前,首先要在LRU list上寻找Free的buffer,如果没有找到,就找最近最少使用端(LRU)的buffer,用以缓存数据。
2、LRU算法及改进
普通LRU算法的实现原理是每访问一个buffer,就把这个buffer移到MRU端。由于在oracle内部,访问buffer的频率非常高,不断的移动buffer在LRU中的位置开销相当大,并且不利于并发。所以oracle对LRU进行了改进。
(1) REPL链表
oracle对传统的LRU算法进行了改进。在每个buffer header增加了一个计数器(称为touch count,简称为TCH)和一个时间戳。每次访问buffer后,如果上次更新buffer header的TCH是在3秒之前,则对TCH加1,并把buffer header中的时间戳更新为当前时间。
oracle为REPL链表增加了一个字段cold_hd,可以理解为cold_hd后面的大部分buffer都是冷块。我们现在读取一个没有被缓存的data block,需要从REPL链表上得到一个buffer来缓存当前的data block。首先从LRU端获取一个buffer,如果是一个冷快(通过TCH来判断),则使用这个buffer缓存当前的data block,并把这个buffer header移到cold_hd的位置,更新TCH为1;如果是一个热快(通过TCH来判断),把这个buffer header移到MRU端,并更新TCH为原来的一半,然后继续检查LRU端的buffer是否是一个冷块,直到找到一个冷块为止。
(2) REPLAX链表
在REPL中淘汰一个buffer需要很多操作,比如判断是否被锁住,是否是dirty块。相对来说,REPL淘汰一个buffer是一个重量级操作。为此,oracle 增加了另外一个链表REPLAX(replacement list auxilitary),这个链表里的所有buffer可以立即被淘汰,不用检查是否是dirty buffer、是否被pinned。从REPLAX链表淘汰buffer是轻量级操作。
oracle启动时,所有buffer header都挂在REPLAX上,随着计数器的增加,这些buffer header会被从REPLAX移到REPL中。当DBWn把dirty块写入到磁盘后,这些buffer会移到到REPLAX。
3、全表扫描
对大表全表扫描时,如果上述改进LRU算法的淘汰策略,会导致所有热快被立即换出。oracle首先会对表大小进行评估,进行大表进行全表扫描时,对大表的buffer采用不同的淘汰策略。
(1)表大小> cache的10%。第一次扫描当作大表来对待,计数器不增加,并且buffer header放入REPLAX链表。如果表数据还在缓存的情况下又进行的全部扫描,则认为这个把这个表当作小表扫描来对待,增加计数器。
(2)表大小> cache的25%。计数器的值不会增加,且表对应的buffer header只会出现在REPLAX链表上。
在create table时,使用cache子句也会影响buffer的淘汰侧路。cache子句包括3个选项CACHE、NOCACHE、CACHE和READS。CACHE适用于经常被访问的小表,NOCACHE适用于大表和访问频次低的表,CACHE READS适用于包含LOB的表。
四、LRU Write List
用户提交commit时, 脏buffer并没有写入dbf文件,而只是记入了redo log中。真正将脏buffer写入到dbf文件中的是DBWRITER。而DBWRITER是怎么个什么样的后台进程呢?
1、DBWRITER进程
Oracle最繁忙的两个后台进程之一,负责将脏buffer写入dbf中。
2、DBWRITER触发条件
后台进程DBwriter,在以下几种条件下触发:
(1)每隔一个时间段触发(例如3秒)。
(2)PGA没有找到干净的块,触发。
(3)当达到扫描块数限制后还没有找到空闲buffer,就需要通知DBW0将脏缓存回写到磁盘。
3、LRU Writer List
有了DBWRITER,那么它需要怎么选择buffer cache中的buffer写入dbf中呢?Oracle引入了LRUW链表。LRUW将脏块按照被访问的频率链起来,方便DBWRITER从冷端开始写入脏buffer。
五、Buffer的状态分析
x$bh 每一个buffer在这个表中都有一行数据。
Buffer Cache中的Buffer有多种状态(x$bh):
0, FREE, no valid block image
1, XCUR,单实例的数据库,直接对应数据库的块。增删改查都需要对该buffer进行操作。
2, SCUR, RAC多实例的数据库才会存在。
3, CR, 当XCUR还没有提交时,其他会话进行读操作时,oracle构造出来的buffer。读完,就没有存在的意义了。
4, READ, oracle正在加载block时,为read状态。
7,WRITE DBWriter正在写。