Oracle数据库技术实用详解学习笔记:Buffer Cache

 

Oracle之所以需要buffer cache,主要有下面3个原因:

a、访问内存的效率远高于从磁盘读取数据的效率,通过缓存数据,减少IO。

b、多进程能共享这些数据,当然,为了保证数据的一致性,需要通过lock、latch机制。

c、通过构造CR 一致性读块,从而提供读一致性功能。

 

下面举个例子说明:

客户端1发送一个sql语句,那么执行语句时,需要从磁盘读取数据放到内存,用完后就把内存中的数据清理掉了,根本不缓存在内存,那么当下次运行相似的语句,也需要访问这些数据时,还得再次从磁盘里读取数据到内存,再进行其他的计算。显然,如果能把这些数据缓存在内存中,那么就不必每次都从缓慢的磁盘中访问数据了,效率大大提高。

如果这时客户端2也发出一个sql语句,那么执行语句时也需要从磁盘读取数据到内存,这些数据被缓存在内存中,过一会又执行相似的语句,就可以很快的访问内存中的数据,但有个问题是,它只能访问自己的内存区域,而不能访问其他服务器进程的私有内存区域,那么怎么办呢?

这时就把这些每个服务器进程都需要共享的内存区域抽取出来,构成一个内存区域,就是buffer cache,当然有些独立于每个服务器进程的信息,还是继续放到每个服务器进程自己私有的内存区域,也就是PGA。这样一来,所有服务器进程在访问数据时,首先要做的就是看看buffer cache里有没有想要的数据,因为虽然它自己没有把需要的数据从硬盘读取到内存,但说不定有其他的服务器进程已经把数据读到内存里了呀,呵呵,运气好的时候,数据已经在内存,于是直接用这些数据就好了。

 

当然啦,虽然可以共享数据,但需要一个内存结构来存储这些数据,同时也需要一个算法来管理这些数据。

 

1、buffer cache的内存结构

buffer cache就像一个水池,水池的最小单位是数据块,当每个数据块被读入到buffer cache时,oracle都会在内存中构建buffer header,并将这些buffer header串联为链表,每个buffer header里的指针指向了所对应的数据块。

在管理buffer header时,oracle用hash算法,把buffer header里记录的数据块地址和数据类型,做为hash函数的参数,得到数据块所属数组号。

把属于同一个hash bucket的所有数据块的buffer header串联起来,构成hash chain,每个hash bucket 都是通过不同的hash chain来体现的。当有多个进程并发访问一个hash chain时,必须要持有cache buffers chain latch。

 

启动数据库后,由oracle自己计算,需要多少个hash bucket。

当执行sql语句时,获取数据块的大致过程:

客户端进程发出select或其他dml语句,oralce会根据sql语句的执行计划,找到符合条件的数据块,然后把请求的数据块地址、数据块的类型做为hash函数的参数,取得数据块所在的hash bucket号,然后进入这个hash chain,从链表上第一个buffer header开始,根据buffer header中记录的指针,找到数据块,然后扫描其中的数据以确认是否是sql语句所需要的块,如果是,则返回块所需的数据;如果不是,则继续往下搜索,一直搜索到最后一个buffer header为止。如果没找到,则调用IO,从数据文件中把该块复制一份到一个可用的buffer里,并构建相应的buffer header,然后把这个buffer cache挂到hash chain上去。

 

上述只是大概的过程,并不准确,只是希望更易于理解,比如,关于如何根据执行计划,找到符合条件的数据块,并没有描述。

实际的情况,肯定是极其复杂的。

 

2、buffer cache 的管理机制

上面讲到的,获取数据块的过程中,如果没找到后,需要调用IO,从磁盘拷贝一个块到可用的buffer,如果有可用的内存空间,当然就没问题,如果找了一段时间,还是没有可用的块,那该怎么办呢?

这里首先需要知道在buffer cache中,有2个list,一个是LRU list,一个是Write list。

LRU list中的数据块分为如下几类:

a、干净的数据块:buffer cache中为空的内存数据块。

b、空闲数据块:buffer cache中没有修改过的数据块。

c、钉住的数据块:buffer cache 中当前正在使用的数据块。

d、脏数据库:buffer cache中已经被修改过的数据块。

 

LRU list中存放了空闲数据块、干净数据块、钉住的数据块,还没有移入write list的脏块。空闲数据块和干净数据块,是可用块。buffer是按照被使用的先后顺序挂到LRU链表上,当某个服务器进程访问一块缓冲区时,就会将其移动到 LRU 列表的最近使用(most recently used,MRU)端。随着更多被访问的缓冲区移动到 LRU 列表的 MRU 端,较早前被访问过的脏缓冲区就会逐渐向 LRU 列表的 LRU 端移动。

当服务器进程需要把磁盘的数据块复制到内存时,需要空闲的数据块,于是开始搜索LRU链表,LRU list上的数据块都会受到cache buffers lru chain latch的保护:

a、在搜索过程中遇到脏数据块时,会把脏数据块移入write list和checkpoint list,之后继续搜索。

b、如果扫描到的buffer正在被使用,那么跳过。

c、当找到可用的缓冲区时,就将数据块从磁盘写入缓冲区,并将此缓冲区移入LRU list的MRU端。

d、如果扫描了一定数量(40%的LRU list)的buffer后,还是没有找到可用的buffer,说明脏块太多了,于是触发DBWn进程,将脏块刷新到数据文件里,刷新完成后,buffer的内容与数据文件里的一致,这些脏块就变为干净的buffer了,于是乎,服务器进程从磁盘中读取数据块,覆盖这个干净的buffer,然后再把这个块挂到LRU list的MRU端。除此之外,当write list超过阀值,也就是达到25%,那么服务器进程就会通知DBWn去写出脏数据块。

 

除了上面d说提到的条件,会触发DBWn去写出脏数据外:

a、每个3秒钟启动一次DBWn。

b、发出完全检查点、增量检查点时,也会触发DBWn。

 

那么这里的检查点是什么呢?

在oracle 8i之前,完全检查点启动时,会标识出buffer cache中所有的脏数据块,然后以最高级启动DBWn进程将这些脏数据写入数据文件。日志切换也会引起完全检查点。在oracle 8i以后,完全检查点只有在两种情况下才会触发:

a、alter   syste   checkpoint命令

b、除了shutdown  abort之外的正常关闭数据库。

注意,日志切换不会触发完全检查点,而是触发增量检查点,增量检查点的条件:

a、每隔3秒

b、发生日志切换。

 

下面举例说明:

现在有个客户端发了一条update语句,服务器端进程收到语句后开始解析、生成了执行计划,接下来开始执行。首先,需要在buffer cache查找到数据;然后,根据修改操作先生成一条重做记录,包含了3个改动向量:回滚段中段头中的事务表的被改动数据块的地址、事务操作代码、数据块的版本号,回滚段中被修改的块的地址、事务操作码、数据块版本号,表中被修改的块地址、事务操作码、版本号,lgwr会把重做记录写入联机日志文件。然后,服务器进程修改数据,提交事务,但这个脏数据块还没有写入磁盘。

 

需要注意的是在checkpoint list上的buffer header还记录了:脏数据块在第一次修改时,所对应的重做条目,在重做日志文件中的地址,也就是LRBA( Low redo block address)。

 

这时,系统发出了增量检查点,ckpt进程会找出当前检查点队列上的第一个buffer header,并将该buffer header 中所记录的LRBA记录到控制文件中去,如果是增量检查点,则还会将LRBA写到每个数据文件头中

 

然后调用DBWn进程,DBWn也会主动扫描LRU list,将发现的脏块移入write list 和checkpoint list中,这里就会把刚才那个脏块移入write list和checkpoint list,然后把checkpoint list中的脏数据写入文件,每个检查点都会由checkpoint queue latch来保护。

 

此时,刚才的脏数据还没有写入文件时,系统突然断电了,系统重启后,从联机日志文件中读取重做记录,那应该从何处开始重做呢?

这里的关键就是ckpt进程写入控制文件的LRBA,会从这个地址开始应用重做记录就可以了,这样需要把检查点之前的重做日志重做,因为这样是没有必要的,由于这个检查点之间的前面一个检查点已经把脏数据写到磁盘了,所以这部分重做记录就不用再应用了,节省了恢复时间。

 

3、实例恢复原理

当系统突然断电,还没有将buffer cache中的脏数据块写入磁盘,同时正在运行的事务也突然中断,事务为中间状态,也就是既没有提交也没有回滚,这样关闭的数据库不是一致的。

下次启动实例时,oracle会有smon进程自动进行实例的恢复:

a、实例启动时,smon进程回去检查控制文件、每个在线的可读写的数据文件的end scn号。数据库在正常运行时,end scn号为空,因为系统还没有正常关闭,只有在关闭时发生了完全检查点时,才会更新end scn。 由于是系统突然断电,系统没有正常关闭,所以来不及更新end scn,所以end scn为空,smon进程就知道实例上次没有正常关闭,于是smon开始进行实例恢复。

b、smon进程首先会从控制文件获取上次检查点的位置,也就是Lrba Low redo block address,于是,在联机日志文件中找到这个地址,从这个位置开始,应用重做日志条目,从而在buffer cache中又恢复了实例在突然断电时的状态,这个过程就是前滚,这个时候buffer cache里,既有已经提交但还没有写入数据文件的脏数据,也还有事务突然中断,这个事务既没有提交也没有回滚所产生的脏数据。

c、前滚后,smon进程立即打开数据库。但是,这时的数据库中还包含那些中间状态的、既没有提交也没有回滚的脏块,于是smon进程开始在后台进行回滚。

d、这时,客户端发出了读取这些数据的请求,这时服务器进程在将这些数据返回时,会由服务器进程进行回滚,回滚完成后再将数据返回给用户。

 

oracle提供了初始化参数 fast_start_mttr_target让我们指定完成实例恢复所花费的时间,这个时间只包含了前滚并打开数据库的时间,不包含回滚时间,这个参数以秒为单位。在数据库运行时,会根据这个时间来估算,在这个时间内大致产生多少量的重做记录,也就决定了检查点的位置。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值