+++++buffer cache 深度解析( 九 )


LGWR将重做条目写入联机日志文件的情况分两种:后台写( write)和同步写(sync write) 。触发后台写的条件有四个:
1)每隔三秒钟,LGWR启动一次;
2)在DBWR启动时,如果发现脏数据块所对应的重做条目还没有写入联机日志文件,则DBWR触发LGWR进程并等待LRWR写完以后才会继续;
3)重做条目的数量达到整个日志缓冲区的1/3时,触发LGWR;
4)重做条目的数量达到1MB时,触发LGWR 。
而触发同步写的条件就一个:当用户提交()时,触发LGWR 。
假如DBWR在写脏数据块的过程中,突然发生实例崩溃 。我们已经知道,用户提交时,是不一定会把提交的数据块写入数据文件的 。那么实例崩溃时,必然会有一些已经提交但是还没有被写入数据文件的内存数据块丢失了 。当实例再次启动时,需要利用日志文件中记录的重做条目在 cache中重新构造出被丢失的数据块,从而完成前滚和回滚的工作,并将丢失的数据块找回来 。于是这里就存在一个问题,就是在日志文件中找重做条目时,到底应该找哪些重做条目?换句话说,应该在日志文件中从哪个起点开始往后应用重做条目?注意,这里所指的日志文件可能不止一个日志文件 。
因为需要随时预防可能的实例崩溃现象,所以在数据库的正常运行过程中,会不断的定位这个起点,以便在不可预期的实例崩溃中能够最有效的保护并恢复数据 。同时,这个起点的选择非常有讲究 。首先,这个起点不能太靠前,太靠前意味着要处理很多的重做条目,这样会导致实例再次启动时所进行的恢复的时间太长;其次,这个起点也不能太靠后,太靠后说明只有很少的脏数据块没有被写入数据文件,也就是说前面已经有很多脏数据块被写入了数据文件,那也就意味着只有在DBWR启动的很频繁的情况下,才能使得 cache中所残留的脏数据块的数量很少 。但很明显,DBWR启动的越频繁,那么所占用的写数据文件的I/O就越严重,那么留给其他操作(比如读取 cache中不存在的数据块等)的I/O资源就越少 。这显然也是不合理的 。
从这里也可以看出,这个起点实际上说明了,在日志文件中位于这个起点之前的重做条目所对应的在 cache中的脏数据块已经被写入了数据文件,从而在实例崩溃以后的恢复中不需要去考虑 。而这个起点以后的重做条目所对应的脏数据块实际还没有被写入数据文件,如果在实例崩溃以后的恢复中,需要从这个起点开始往后,依次取出日志文件中的重做条目进行恢复 。考虑到目前的内存容量越来越大, cache也越来越大, cache中包含几百万个内存数据块也是很正常的现象的前提下,如何才能最有效的来定位这个起点呢?
为了能够最佳的确定这个起点,引入了名为CKPT的后台进程,通常也叫作检查点进程( ) 。这个进程与DBWR共同合作,从而确定这个起点 。同时,这个起点也有一个专门的名字,叫做检查点位置( ) 。
为了在检查点的算法上更加的具有可扩展性(也就是为了能够在巨大的 cache下依然有效工作),引入了检查点队列( queue),该队列上串起来的都是脏数据块所对应的。
而DBWR每次写脏数据块时,也是从检查点队列上扫描脏数据块,并将这些脏数据块实际写入数据文件的 。当写完以后,DBWR会将这些已经写入数据文件的脏数据块从检查点队列上摘下来 。这样即便是在巨大的 cache下工作,CKPT也能够快速的确定哪些脏数据块已经被写入了数据文件,而哪些还没有写入数据文件,显然,只要在检查点队列上的数据块都是还没有写入数据文件的脏数据块 。
而且,为了更加有效的处理单实例和多实例(RAC)环境下的表空间的检查点处理,比如将表空间设置为离线状态或者为热备份状态等,还专门引入了文件队列(file queue) 。文件队列的原理与检查点队列是一样的,只不过每个数据文件会有一个文件队列,该数据文件所对应的脏数据块会被串在同一个文件队列上;