Buffer Cache的内存结构

Buffer Cache概述

Buffer CacheSGA的一部分,Oracle利用Buffer Cache来管理data blockBuffer Cache的最终目的就是尽可能的减少磁盘I/O

buffer cache所能提供的功能主要包括:

通过缓存数据块,从而减少I/O

通过构造CRConsistent Read)块,从而提供读一致性功能。

通过提供各种locklatch机制,从而提供多个进程并发访问同一个数据块的功能。

 

内存里的数据块通常叫做buffer,而数据文件里的数据块通常叫做block,二者是一个意思。一般我们会混用这两个名词。

Buffer Cache 中的管理结构

Buffer Cache中主要有3大结构用来管理Buffer Cache

            1Hash Bucket  Hash Chain List Hash BucketHash Chain List用来实现data block的快速定位。

 

buffer cache就像一个水池,水池的最小单位就是数据块。当每个数据块被读入buffer cache时,Oracle都会抽取数据块的头部,在内存中构建buffer header,并将这些buffer header串成链表的形式。而buffer header里面记录的指针就指向buffer cache中的该数据块本身。于是,Oracle在搜索某个数据块时,就不用去buffer cache中找,而是直接扫描链表上该数据块所对应的buffer header,然后根据找到的buffer header所记录的指针就能到buffer cache中直接定位该数据块了。

 

在管理buffer header的过程中,Oracle同样借助了hash算法。通过对buffer header里记录的数据块地址和数据块类型运用hash函数以后,得到该数据块所属的组号。

 

这里的hash chain就是属于同一个hash bucket的所有buffer header所串起来的链表。实际上,hash bucket只是一个逻辑上的概念。每个hash bucket都是通过不同的hash chain体现出来的。每个hash chain都会由一个cache buffers chains latch来管理其并发操作。

 

启动数据库以后,Oracle究竟产生多少个hash bucket,则由Oracle自己计算。

 

当前台进程发出SELECT或者其他DML语句时,Oracle根据SQL语句的执行计划找到符合SQL条件的数据块,然后Oracle会根据对请求的数据块的地址以及数据块的类型作为参数,应用hash函数以后,得到要找的数据块所处的hash bucket,也就是确定该数据块在哪条hash chain上。然后,Oracle进入该hash chain,从上面所挂的第一个buffer header开始,根据buffer header所含有的指针找到对应的块体,然后扫描其中的数据,确认其是否是SQL语句所需要的块,如果是,则返回该块里所需要的数据;否则,如果不是,则继续往下搜索,一直搜索到最后一个buffer header为止。如果一直都没有找到,则调用物理I/O,到数据文件里把该块所含有的内容复制一份到一个可用的buffer里,并构建该块的buffer header,然后将该buffer header挂到hash chain上去。

 

            2LRU List :挂载有指向具体的free buffer, pinned buffer以及还没有被移动到 write listdirty buffer 等信息。所谓的free buffer就是指没有包含任何数据的buffer,所谓的pinned buffer,就是指当前正在被访问的buffer

 

在前面,我们已经知道了Oracle是如何在hash chain中搜索要找的数据块所对应的buffer header的过程,我们也知道如果在hash chain上没有找到所要的buffer header时,Oracle会发出I/O调用,到磁盘上的数据文件中获取数据块,并将该数据块的内容复制一份到buffer cache中的内存数据块里。这个时候,假如buffer cache是空的,比较好办,直接拿一个空的内存数据块来用即可。但是如果buffer cache中的内存数据块全都被用掉了,没有空的内存数据块了,怎么办?应该重新使用哪一个内存数据块?当然我们可以逐个比较内存数据块与其对应在数据文件中的数据块的内容是否一致,如果一致则可以将该数据块拿来,将其内容清空,然后将当前数据块的内容复制进入;如果不一致,则说明数据块在内存里被修改了,但是还没有写入数据文件,因此该数据块不能被其他内容覆盖,则跳过,再找下一个。毫无疑问,这种方式效率低下。为了高效地管理buffer cache中的内存数据块,Oracle引入了LRU链表等结构。

 

buffer cache中,最耳熟能详的链表可能就是LRU链表了。在介绍LRU前,先说明几个概念。

 

 

脏数据块(dirty buffer):buffer cache中的内存数据块的内容被修改,从而导致与数据文件中的数据块的内容不一致。

空闲数据块(free buffer):buffer cache中的内存数据块为空。

干净数据块(clean buffer):buffer的内容与数据文件中的一致。

钉住的数据块(pin buffer):当前正在更新的内存数据块。

数据库写进程(DBWR):这是一个很底层的数据库后台进程。既然是后台进程,就表示该进程是不能被用户调用的。由Oracle内置的一些事件根据需要启动该进程,该进程用来将脏数据块写入磁盘上的数据文件。

 

对于空闲数据块和干净数据块,我们一般都统称为可用数据块,因为其中的内容可以被新的数据内容覆盖。其他状态的数据块,比如脏数据块,则不能被新的内容覆盖。

 

LRU表示Least Recently Used,也就是指最近最少使用的buffer header链表。LRU链表串联起来的buffer header都指向可用数据块。buffer按照被使用的先后顺序挂在LRU链表上,先被使用的buffer挂在LRU链表的后面,后被使用的buffer则被挂在LRU链表的前面。如果bufferDML语句修改了,则该buffer会从LRU链表上摘下来。换句话说,LRU链表上的buffer header所指向的buffer都是可用数据块。

 

当服务器进程无法找到空的buffer来存放新的数据请求时,则需要把已经存放了数据的buffer拿来使用,也就是用新的数据块的内容覆盖曾经使用过的buffer。在查找应该覆盖哪个buffer时,Oracle会在LRU链表上的尾部开始扫描,如果扫描到的buffer正在被使用,则跳过该buffer,继续往下找,直到找到为止。如果扫描了一定数量的buffer以后还没找到可用的buffer,则说明脏块太多了,于是触发DBWn进程,将脏块刷新到数据文件里,刷新完毕以后,buffer的内容与数据文件里的一致,于是这些脏块就变成干净的buffer了,也就可以拿来覆盖其中的内容了。这些干净的buffer就会挂在LRU链表的尾部,供进程所使用。

 

当进程在LRU链表上扫描可用数据块时,会受到cache buffers lru chain latch的保护。

            3Write(Dirty)List :挂载有指向具体的 dirty block的信息。所谓的dirty block,就是指在 buffer cache中被修改过但是还没有被写入到磁盘的block

 

 

请使用浏览器的分享功能分享到微信等