为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

20-buffer cache深度分析

2013-08-09 50页 ppt 617KB 54阅读

用户头像

is_982925

暂无简介

举报
20-buffer cache深度分析nullnullbuffer cache的概念 用最简单的语言来描述oracle数据库的本质,其实就是能够用磁盘上的一堆文件来存储数据,并提供了各种各样的手段对这些数据进行管理。 作为管理数据的最基本要求就是能够保存和读取磁盘上的文件中的数据。众所周知,读取磁盘的速度相对来说是非常慢的,而内存相对速度则要快的多。 因此为了能够加快处理数据的速度,oracle必须将读取过的数据缓存在内存里。而oracle对这些缓存在内存里的数据起了个名字:数据高速缓存区(db buffer cache),通常就叫做buffer cache。 ...
20-buffer cache深度分析
nullnullbuffer cache的概念 用最简单的语言来描述oracle数据库的本质,其实就是能够用磁盘上的一堆文件来存储数据,并提供了各种各样的手段对这些数据进行管理。 作为管理数据的最基本要求就是能够保存和读取磁盘上的文件中的数据。众所周知,读取磁盘的速度相对来说是非常慢的,而内存相对速度则要快的多。 因此为了能够加快处理数据的速度,oracle必须将读取过的数据缓存在内存里。而oracle对这些缓存在内存里的数据起了个名字:数据高速缓存区(db buffer cache),通常就叫做buffer cache。 按照oracle官方的说法,buffer cache就是一块含有许多数据块的内存区域,而这些数据块主要都是数据文件里的数据块内容的拷贝。 通过初始化参数:buffer_cache_size来指定buffer cache的大小。oracle实例一旦启动,该区域大小就被分配好了。nullbuffer cache所能提供的功能主要包括: 1) 通过缓存数据块,从而减少I/O。 2) 通过构造CR块,从而提供读一致性功能。 3) 通过提供各种lock、latch机制,从而提供多个进程并发访问同一个数据块的功能。nullbuffer cache概述 oracle内部在实现其管理的过程中,有两个非常有名的名词:链和hash算法。 链表是一种数据结构,通过将对象串连在一起,从而构成链表结构。这样,如果要修改、删除、查找某个对象的话,都可以先到链表中去查找,而不必实际的访问物理介质。 oracle中最有名的链表大概就是LRU链表了,我们后面会介绍它。nullhash算法则是为了能够进行快速查找定位所使用一种技术。 所谓hash算法,就是根据要查找的值,对该值进行一定的hash算法后得出该值所在的索引号,然后进入到该值应该存在的一列数值列表(可以理解为一个二维数组)里,通过该索引号去找它应该属于哪一个列表。 然后再进入所确定的列表里,对其中所含有的值,进行一个一个的比较,从而找到该值。 这样就避免了对整个数值列表进行扫描才能找到该值,这种全扫描的方式显然要比hash查找方式低效很多。其中,每个索引号对应的数值列在oracle里都叫做一个hash bucket。null我们来列举一个最简单的hash算法。 假设我们的数值列表最多可以有10个元素,也就是有10个hash buckets,每个元素最多可以包含20个数值。 则对应的二维数组就是t[10][20]。我们可以定义hash算法为n MOD 10。 通过这种算法,可以将所有进入的数据均匀放在10个hash bucket里面,hash bucket编号从0到9。 比如,我们把1到100都通过这个hash函数均匀放到这10个hash bucket里,当查找32在哪里时,只要将32 MOD 10等于2,这样就知道可以到2号hash bucket里去找,也就是到t[2][20]里去找,2号hash bucket里有10个数值,逐个比较2号hash bucket里是否存在32就可以了。nullbuffer cache就是使用多个hash bucket来管理的,其hash算法当然比我们前面列举的要复杂多了。 我们先来看下面这个图。这副图从逻辑上说明了整个buffer cache的结构是怎么样的。这副图的右上角列出了三个名词:hash bucket、buffer header和hash chain。null这里的hash bucket就是我们前面说明hash算法中提到的二维数组的第一维。它是通过对buffer header里记录的数据块地址和数据块类型运用hash算法以后,得到的组号。 这里的hash chain就是属于同一个hash bucket的所有buffer header所串起来的链表。实际上,hash bucket只是一个逻辑上的概念。每个hash bucket都是通过不同的hash chain而体现出来的。每个hash chain都会由一个cache buffers chains latch来管理其并发操作。null对于buffer header来说,每一个数据块在被读入buffer cache时,都会先在buffer cache中构造一个buffer header,buffer header与数据块一一对应。buffer header包含的主要信息有: 1) 该数据块在buffer cache中实际的内存地址。就是上图中的虚线箭头所表示的意思。 2) 该数据块的类型,包括data、segment header、undo header、undo block等等。 3) 该buffer header所在的hash chain,是通过在buffer header里保存指向前一个buffer header的指针和指向后一个buffer header的指针的方式实现的。 4) 该buffer header所在的LRU、LRUW、CKPTQ等链表(这些链表我们后面都会详细说明)。也是通过记录前后buffer header指针的方式实现。 5) 当前该buffer header所对应的数据块的状态以及标记。 6) 该buffer header被访问(touch)的次数。 7) 正在等待该buffer header的进程列表(waiter list)和正在使用该buffer header的进程列表(user list)。 buffer cache中,缺省的hash bucket的数量或者说缺省有多少条hash chain链表,是由一个隐藏参数: _db_block_hash_buckets决定的。置于该参数的取值,8i下,该参数缺省为db_block_buffers×2;但是到了9i以后,该参数取的是小于且最接近于db_block_buffers×2的素数。null转储buffer cache 就象实例中的其他内存结构一样,oracle提供了可以将buffer cache转储到跟踪文件的。方法如下: 这里的level有很多值,分别可以转储buffer cache中的不同的内容。level的可选值包括: 1 只转储buffer header 2 在level 1的基础上再转储数据块头 3 在level 2的基础上再转储数据块内容 4 转储buffer header和hash chain 5 在level 1的基础上再转储数据块头和hash chain 6 在level 2的基础上再转储数据块内容和hash chain 8 转储buffer header和hash chain以及users/waiters链表 9 在level 1的基础上再转储数据块头、hash chain以及users/waiters链表 10 在level 2的基础上再转储数据块内容、hash chain以及users/waiters链表null我们创建一个简单的测试表,然后看看转储出来的buffer header是什么样子的。null这时我们知道buffer_test表的object_id是54286 ,同时,该表中只有2个block具有数据。 1个是segment header,另一个就是实际存放了1这个值的数据块。接着我们把buffer header转储出来:到user_dump_dest所定义的目录下,找到跟踪文件并打开,可以看到类似下面的信息,这里我们列出前两个buffer header以及我们建立的object_id为54286的buffer_test表所对应的buffer header的内容:null这个BH的hash值 指向前一个BH的hash 指向后一个BH的hash 可以判断是否在一个chain上,进而判断是否在一个Bucket上。同样的,我们还可以看到类似结构的lru、ckptq、fileq,这些都是管理buffer header的一些链表结构。null我们来看我们创建的buffer_test表所对应的buffer header。 首先,我们看到class,表示该buffer header所对应的数据块的类型,具体的值与含义的对应为: 1=data block; 2=sort block; 3=save undo block; 4=segment header; 5=save undo header; 6=free list; 7=extent map; 8=1st level bmb; 9=2nd level bmb; 10=3rd level bmb; 11=bitmap block; 12=bitmap index block; 13=unused; 14=undo header; 15=undo block。 我们可以看到与buffer_test表相关buffer header有两个:一个是4(segment header),另一个是1(data block)。null我们看到rdba,这表示buffer header所对应的数据块的地址。我们可以看到class为1的buffer header的rdba为0x00410452 (1/66642) 。说明该数据块的位置是1号文件的66642号block里。004表示数据文件号乘以4,而10452表示数据块的号。我们看到,该buffer header指向的就是1号文件里的66642号数据块。我们可以再来看看表buffer_test里的rowid所告诉我们的文件号以及数据块号,从下面可以看到,结果是一样的。这个对象的obj=54286null这个是对象的segment header所对应的buffer handle。 Obj=54286null我们可以来看一下st,这表示buffer cache所指向的数据块的状态。 一共有六种状态: FREE(0)=可以被重用的数据块; XCURRENT(1)=实例以排他方式获取的当前模式数据块; SCURRENT(2)=可以与其他实例共享的当前模式数据块; CR(3)=作为一致性读镜像的数据块,永远不会被写入磁盘; READING(4)=正在从磁盘读出的数据块; MRECOVERY(5)=正在进行介质恢复的数据块; IRECOVERY(6)=正在进行实例恢复的数据块。 从状态说明中我们可以看到,现在表buffer_test的数据块都是当前模式的数据块。我们可以来构造一个CR状态的数据块。 这个Buffer header所指向的数据块的状态。null分别建立两个session,在一个session中,执行: 不要提交,然后在另外一个session中,执行:null我们转储buffer header后,到跟踪文件中找到obj为54286的记录,可以看到类似如下的内容。可以看到该buffer header的状态就是CR。另外,我们还可以看到tch,就是表示该数据块被扫描的次数。以上这些是转储出来的内容。Oracle还提供了视图来显示buffer header的内容,这就是X$BH。这个视图就是把转储到平面文件以后所看到的诸如hash、st、tch等的值以列的方式呈现出来。nullbuffer cache的内部管理机制 在buffer cache中获取所需要的数据块的过程 当前台进程发出SELECT或者其他DML语句时,oracle根据SQL语句的执行计划确定所要找的数据块,会构造一个名为数据块描述(buffer descriptor)的内存结构。 该buffer descriptor位于session的PGA中,所包含的内容主要是数据块所在的物理地址、数据块的类型、数据块所属对象的object id等信息。 随后,oracle会把对数据块请求的锁定模式以及所构造出来的buffer descriptor传入专门搜索数据块的函数中。在该函数中,oracle根据buffer descriptor所记录的信息,应用hash算法以后,得到要找的数据块所处的hash bucket,也就是确定该数据块在哪条hash chain上。然后,oracle进入该hash chain,从上面所挂的第一个buffer header开始搜索,一直搜索到最后一个buffer header。 null在hash chain上搜索的逻辑如下:   1) 比较buffer header上所记录的数据块的地址,如果不符合,则跳过该buffer header。   2) 跳过状态为CR的buffer header。   3) 如果遇到状态为READING的buffer header,则等待,一直等到该buffer header的状态改变以后再比较所记录的数据块的地址是否符合。   4) 如果发现数据块地址符合的buffer header,则查看该buffer header是否位于正在使用的列表上,如果是,则判断已存在的锁定模式与当前所要求的锁定模式是否兼容,如果是,则返回该buffer header所记录的数据块地址,并将当前进程号放入该buffer header所处的正在使用的列表上。   5) 如果发现锁定模式不兼容,则根据找到的buffer header所指向的数据块的内容,构建一个新的、内容一样的、状态为XCURRENT的复制数据块,并且构造一个状态为CR的buffer header,同时该buffer header指向所新建立的复制数据块。然后,返回该复制数据块的地址,并将当前进程号放入该buffer header所处的正在使用的列表上。   6) 如果比较完整个hash chain以后还没发现所要找的buffer header,则从磁盘上读取数据文件。并将读取到的数据块所对应的buffer header挂到hash chain上。nullLRU和LRUW链表结构概述 在前面,我们已经知道了oracle是如何在hash chain中搜索要找的数据块所对应的buffer header的过程,我们也知道如果在hash chain上没有找到所要的buffer header时,oracle会发出I/O调用,到磁盘上的数据文件中获取数据块,并将该数据块的内容拷贝一份到buffer cache中的内存数据块里(顺带提一句,内存数据块通常叫做buffer,而数据文件里的数据块通常叫做block,二者是一个意思)。这个时候,假如buffer cache是空的,比较好办,直接拿一个空的内存数据块来用即可。但是如果buffer cache中的内存数据块全都被用掉了,没有空的内存数据块了,怎么办?应该重新使用哪一个内存数据块?当然我们可以一个一个的比较内存数据块与其对应在数据文件中的数据块的内容是否一致,如果一致则可以将该数据块拿来,将其内容清空,然后拷贝上当前数据块的内容;如果不一致,则跳过,再找下一个。毫无疑问,这种方式效率低下。为了高效的管理buffer cache中的内存数据块,oracle引入了LRU和LRUW等链表等结构。null在buffer cache中,最耳熟能详的链表可能就是LRU链表了。在前面描述buffer cache结构的图上,也可以看到有两个链表:LRU和LRUW。在介绍LRU和LRUW前,先说明几个概念。     1) 脏数据块(dirty buffer):buffer cache中的内存数据块的内容与数据文件中的数据块的内容不一致。     2) 可用数据块(free buffer):buffer cache中的内存数据块为空或者其内容与数据文件中的一致。注意,可用数据块不一定是空的。     3) 钉住的数据块(ping buffer):当前正在更新的内存数据块。     4) 数据库写进程(DBWR):这是一个很底层的数据库后台进程。既然是后台进程,就表示该进程是不能被用户调用的。由oracle内置的一些事件根据需要启动该进程,该进程用来将脏数据块写入磁盘上的数据文件。 LRU表示Least Recently Used,也就是指最近最少使用的buffer header链表。LRU链表串连起来的buffer header都指向可用数据块。而LRUW则表示Least Recently Used Write,也叫做dirty list,也就是脏数据块链表,LRUW串起来的都是修改过但是还没有写入数据文件的内存数据块所对应的buffer header。某个buffer header要么挂在LRU上,要么挂在LRUW上,不能同时挂在这两个链表上。null随着硬件技术的发展,电脑的内存越来越大。buffer cache也是越来越大,只用一条LRU和一条LRUW来管理buffer header已经不够用了。 同时oracle还引入了多个DBWR后台进程来帮助将buffer cache中的脏数据块写入数据文件,显然,多个DBWR后台进程都去扫描相同的LRUW链表会引起争用。 为此oracle引入了working set的概念。每个working set都具有它自己的一组LRU和LRUW链表。每个working set都由一个名为“cache buffers lru chain”的latch(也叫做lru latch)来管理,所以从这个意义上说,每一个lru latch就是一个working set。而每个被加载到buffer cache的buffer header都以轮询的方式挂到working set上去。也就是说,当buffer cache加载一个新的数据块时,其对应的buffer header会去找一个可用的lru latch,如果没有找到,则再找下一个lru latch,直到找到为止。如果轮询完所有的lru latch也没能找到可用的lru latch,该进程只有等待latch free等待事件,同时出现在v$session_wait中,并增加“latch misses”。如果启用了多个DBWR后台进程的话,每个DBWR进程都会对应一个不同的working set,而且每个DBWR只会处理分配给它的working set,不会处理其他的working set。 我们已经知道一个lru latch就是一个working set,那么working set的数量也就是lru latch的数量。而lru latch的数量是由一个隐藏参数:_db_block_lru_latches决定的。该参数缺省值为DBWR进程的数量×8。 该参数最小必须为8,如果强行设置比8小的数值,oracle将忽略你设置的值,而使用8作为该参数值。null我们已经知道LRU链表是用来查找可以重用的内存数据块的,那么oracle是怎么使用LRU链表的呢?这里需要分为8i之前和8i以后两种情况。 在8i之前,我们举一个例子。假设buffer cache只能容纳4个数据块,同时只有一个hash chain和一个LRU。当数据库刚刚启动,buffer cache是空的。这时前台进程发出SELECT语句获取数据块时,oracle找一个空的内存数据块,并将其对应的buffer header挂到hash chain上。同时,oracle还会把该buffer header挂到LRU的最尾端。随后前台进程又发出SELECT语句,这时所找到的buffer header在LRU上会挂到前一个buffer header的后面,也就是说第二次SELECT语句所找到的buffer header现在变成了LRU的最尾端了。假设发出4句SELECT以后找到了4个buffer header,从而用完了所有的buffer cache空间。这个时候的LRU可以用下图来表示。null这个时候,发来了第五句SELECT语句。这时的buffer cache里已经没有空的内存数据块了。但是既然需要容纳下第五个数据块,就必然需要找一个可以被替换(后面会看到类似牺牲、重用的字样,它们和替换都是一个意思)的内存数据块。这个内存数据块会到LRU上去找。按照oracle设定的最近最少使用的原则,位于LRU最尾端的BH1将成为牺牲者,oracle会把该BH1对应的内存数据块的内容清空,并将当前第五句SQL所获得的数据块的内容拷贝进去。这个时候,BH1就成了LRU的首端,而BH2则成为了LRU的尾端。如下图所示。在这种方式下,经常被访问的数据块可以一直靠近LRU的首端,也就保证了这些数据块可以尽可能的不被替换掉,从而保证了访问的效率。null到了8i以后,oracle引入了一种更加复杂的机制来管理LRU上的数据块。 8i以后,LRU和LRUW链表都具有两个子链表,分别叫做辅助链表和主链表。同时还对buffer header增加了一个属性:touch数量,也就是每个buffer header曾经被访问过的次数,来对LRU链表进行管理。 oracle每访问一次buffer header,就会将该buffer header上的touch数量增加1,因此,touch数量“近似”的体现了某个内存数据块总共被访问的次数。 注意,这只是近似,并不精确。因为touch的增加并没有使用latch来管理并发性。这只是一个大概值,表示趋势的,不用百分百的精确。 还是用上面的这个例子来说明。还是假设buffer cache只能容纳4个数据块,同时只有一个hash chain和一个LRU(确切的说应该是一对LRU主链表和辅助链表)。读入第一个数据块时,该数据块对应的buffer header会挂到LRU辅助链表(注意,这里是辅助链表,而不是主链表)的最末端,同时touch数量为1。读取第二个不同的数据块时,该数据块对应的buffer header会挂到前一个buffer header的后面,从而位于LRU辅助链表的最末端,同样touch为1。假设4个数据块全都用完以后的LRU链表可以用下图四描述。每个buffer header的touch数量都为1。BH1T1BH4T1null从上图中我们可以看到辅助LRU链表都挂满了,而主LRU链表还是空的。这个时候,前台发出第五句SQL语句,要求返回指定的数据块。这时,oracle发现buffer cache里已经没有空的内存数据块了,于是从辅助LRU链表的尾部开始扫描,也就是从BH1开始扫描,以查找可以被替代的数据块。扫描的过程中按照下面的逻辑来选择被牺牲的(也就是可以被替代的)数据块: 1)如果被扫描到的buffer header的touch数量小于隐藏参数_db_aging_hot_criteria(该参数缺省为2)的值,则选中该buffer header作为牺牲者,并立即返回该buffer header所含有的数据块的地址。 2) 如果当前buffer header的touch数量大于_db_aging_hot_criteria的值,则不会使用该buffer header。但是如果当前的_db_aging_stay_count的值小于_db_aging_hot_criteria的值,则会将当前该buffer header的touch值赋值给_db_aging_stay_count;否则将当前buffer header的touch数量减掉一半。 按照上述的逻辑,这时将选出BH1作为牺牲者(因为BH1的touch数量为1,小于_db_aging_hot_criteria的值),并将其对应的内存数据块的内容清空,同时将当前第五个数据块的内容拷贝进去。但是这里要注意,这个时候该BH1在LRU链表上的位置并不会发生任何的变化。而不会像8i之前的那样,BH1变成LRU链表的首端。null接下来,前台发来了第六句和第七句SQL,分别要返回与第五句和第四句SQL一样的数据块,也就是要返回当前的BH1和BH4。这个时候,oracle会增加BH1和BH4的touch数量,同时将该BH1和BH4从辅助LRU链表上摘下,转移到主LRU链表的中间位置。可以用下图描述。这个时候,如果发来了第八句SQL,要求返回与第三句SQL相同的数据块,也就是当前的BH3,则这时该BH3会插入主LRU链表上的BH1和BH4中间,注意每次向主LRU列表插入buffer header时都是向中间位置插入。如果发来了第九句SQL要求返回BH2,则我们可以知道,BH2会转移到主LRU链表的中间。这个时候,辅助LRU链表就空了,没有buffer header了。null这时,如果又发来第十句SQL,要求返回一个新的、buffer cache中不存在的数据块时。oracle会先扫描辅助LRU链表,发现上面没有任何的buffer header时,则必须扫描主LRU链表。从尾部开始扫描,采用前面说到的与扫描辅助LRU链表相同的规则挑选牺牲者。挑出的可以被替代的buffer header将从主LRU链表上摘下,放入辅助LRU链表。 从上面所描述的buffer header在辅助LRU链表和主LRU链表之间交替的过程中,我们可以看出,oracle改进LRU链表的管理方式的目的,就是想千方百计的能够将多次被访问的数据块保留在内存里,同时又要平衡有限的内存资源。这种方式相比较8i之前而言,无疑是进步很多的。在8i之前中,某个数据块可能只会被访问一次,但是就这么一次的访问就将该数据块放到了LRU的首端,从而可能就挤掉了一个LRU上不是那么经常被访问,但是也会多次访问的数据块。而8i以后,将访问一次的数据块和访问一次以上的数据块彻底分开,而且查找可用数据块时,始终都是从辅助LRU链表开始扫描。实际上也就使得越倾向于只访问一次的数据块越快的从内存中清理出去。nullLRUW链表管理 从前面我们已经知道SELECT语句读取数据块到buffer cache的过程。那么我们必然会产生另外一个疑问,就是当使用DML等语句修改了buffer cache里的内存数据块以后的过程是怎样的?实际上,为了能够最有效、安全的完成将内存数据块写入数据文件的过程,oracle提供了比读取数据块更为复杂的机制。   我们已经知道LRUW表示脏数据块链表,该链表上的buffer header指向的都是已经从LRU链表上摘下来、其对应的内存数据块里的内容已经被修改、但是还没有被写入数据文件的内存数据块。在这些脏数据块在能够被重用之前,它们必须要被DBWR写入磁盘。从8i以后,LRUW链表同样包含两个子链表:辅助LRUW链表和主LRUW链表。那么LRUW链表是如何产生buffer header的呢?oracle又是如何对其进行管理的呢?null我们还是接着上面图所示的例子来说明。假设这个时候,前台用户发出DML语句,要求修改BH2所指向的内存数据块。这时,按顺序发生下面的动作: 1) oracle会将BH2从辅助LRU链表上摘下,同时插入主LRU链表的中间,也就是插入BH1和BH4中间,同时增加BH2的touch的数量。 2) 将该BH2的标记设置为钉住(ping)。 3) 更新BH2对应的内存数据块的内容。 4) 更新完以后,取消钉住的标记。 5) 将BH2从主LRU链表转移到主LRUW链表上。 6) 如果这个时候又有进程发出更新BH2所对应的内存数据块的内容,则BH2再次被钉住,更新,取消钉住。 7) DBWR启动以后,在扫描主LRUW链表时会将BH2转移到辅助LRUW链表上。 8) DBWR将辅助LRUW链表上的BH2对应的数据块写入数据文件。 9) 确认成功写入数据文件以后,将BH2从辅助LRUW链表上转移到辅助LRU链表上。null从上面的描述中,我们可以看到,主LRUW链表上包含的buffer header要么是已经更新完了的数据块,要么是被钉住正在更新的数据块。 而当DBWR进程启动以后,它会扫描主LRUW链表,并跳过正在被钉住更新的buffer header,而将已经更新完了的buffer header从主LRUW链表上摘除,并转移到辅助LRUW链表上去。 扫描完主LRUW链表,或扫描的buffer header的个数达到一定限度时,DBWR会转到辅助LRUW上,将上面的buffer header所对应的数据块写入数据文件。所以说,对于辅助链表上的buffer header来说,要么是正在等待被写入的;要么就是已经发出写入请求,正在写入而还没写完的。这里要注意的是,buffer header进入LRUW链表,是从尾端进入;而DBWR扫描LRUW链表时,则是从首端开始。null顺带提一句,这里将主LRUW链表和辅助LRUW链表分开,主要就是为了提高DBWR在主LRUW链表上扫描的效率。 如果只有主LRUW链表而没有辅助LRUW链表的话,势必造成三种类型buffer header交织在LRUW链表上:1)正在被钉住更新的buffer header;2)已经更新完,而正在等待被写入数据文件的buffer header;3)已经发出写请求,正在写而尚未写完的buffer header。在这种情况下,必然造成DBWR为了找到第二种类型的buffer header而需要扫描不该扫描的第三种类型的buffer header。nullDBWR进程 我们已经知道DBWR进程负责将脏数据块写入磁盘。它是一个非常重要的进程。 select c.sid,a.name,a.description from v$bgprocess a ,v$process b , v$session c where a.paddr=b.addr and b.addr = c.paddr;null随着内存的不断增加,1个DBWR进程可能不够用了。 所以从8i起,我们可以为系统配置多个DBWR进程。初始化参数:db_writer_processe决定了启动多少个DBWR进程。 每个DBWR进程都会分配一个lru latch,也就是说每个DBWR进程对应一个working set。因此oracle建议配置的DBWR进程的数量应该等于lru latch的数量,同时应该小于CPU的数量。系统启动时,就确定好了working set与DBWR进程的对应关系,每个DBWR进程只会将分配给自己的working set上的脏数据块写入数据文件。 注意:每个latch对应多个lru chain.nullDBWR作为一个后台进程,只有在某些条件满足了才会触发。这些条件包括: 1) 当进程在辅助LRU链表和主LRU链表上扫描以查找可以覆盖的buffer header时,如果已经扫描的buffer header的数量到达一定的限度(由隐藏参数:_db_block_max_scan_pct决定)时,触发DBWR进程。_db_block_max_scan_pct表示已经扫描的buffer header的个数占整个LRU链表上buffer header总数的百分比。这时,搜索可用buffer header的进程挂起,在v$session_wait中表现为等待“free buffer wait”事件,同时增加v$sysstat中的“dirty buffers inspected”的值。 2) 当DBWR在主LRUW链表上查找已经更新完而正在等待被写入数据文件的buffer header时,如果找到的buffer header的数量超过一定限度(由隐藏参数:_db_writer_scan_depth_pct决定)时,DBWR就不再继续往下扫描了,而转到辅助LRUW链表上将其上的脏数据块写入数据文件。_db_writer_scan_depth_pct表示已经扫描的脏数据块的个数占整个主LRUW链表上buffer header总数的百分比。null3) 如果主LRUW链表和辅助LRUW链表上的脏数据块的总数超过一定限度,也将触发DBWR进程。该限度由隐藏参数:_db_large_dirty_queue决定。 4) 发生增量检查点(incremental checkpoint)或完全检查点(complete checkpoint)时触发DBWR。 5) 每隔三秒钟启动一次DBWR。 6) 将表空间设置为离线(offline)状态时触发DBWR。 7) 发出命令:alter tablespace … begin backup,从而将表空间设置为热备份状态时触发DBWR。 8) 将表空间设置为只读状态时,触发DBWR。 9) 删除对象时(比如删除某个表)会触发DBWR。null当DBWR要写脏数据块时,并不是说立即将所有的脏数据块都同时写入磁盘。 为了尽量减少物理的I/O的次数,DBWR会将要写的脏数据块所对应的buffer header拷贝到一个名为批量写(write batch)的结构中。每个working set所对应的DBWR进程都可以向该结构里拷贝buffer header。当write batch的buffer header的个数达到一定限额时,才会发生实际的I/O,从而将脏数据块写入磁盘。这个限额为硬件平台所能支持的同时并发的异步I/O的最大数量。8i之前是可以用隐藏参数(_db_block_write_batch)来控制这个限额的。但是8i以后,取消了该参数,而由oracle自己来计算。nullDBWR、CKPT、LGWR进程之间的合作 将内存数据块写入数据文件实在是一个相当复杂的过程,在这个过程中,首先要保证安全。 所谓安全,就是在写的过程中,一旦发生实例崩溃,要有一套完整的机制能够保证用户已经提交的数据不会丢失; 其次,在保证安全的基础上,要尽可能的提高效率。众所周知,I/O操作是最昂贵的操作,所以应该尽可能的将脏数据块收集到一定程度以后,再批量写入磁盘中。 直观上最简单的解决方法就是,每当用户提交的时候就将所改变的内存数据块交给DBWR,由其写入数据文件。这样的话,一定能够保证提交的数据不会丢失。但是这种方式效率最为低下,在高并发环境中,一定会引起I/O方面的争用。oracle当然不会采用这种没有扩展性的方式。 oracle引入了CKPT和LGWR这两个后台进程,这两个进程与DBWR进程互相合作,提供了既安全又高效的写脏数据块的解决方法。null用户进程每次修改内存数据块时,都会在日志缓冲区(redo buffer)中构造一个相应的重做条目(redo entry),该重做条目描述了被修改的数据块在修改之前和修改之后的值。而LGWR进程则负责将这些重做条目写入联机日志文件。只要重做条目进入了联机日志文件,那么数据的安全就有保障了,否则这些数据都是有安全隐患的。LGWR 是一个必须和前台用户进程通信的进程。LGWR 承担了维护系统数据完整性的任务,它保证了数据在任何情况下都不会丢失。 LGWR将重做条目写入联机日志文件的情况分两种: 后台写(background write)和同步写(sync write)。 触发后台写的条件有四个:1)每隔三秒钟,LGWR启动一次;2)在DBWR启动时,如果发现脏数据块所对应的重做条目还没有写入联机日志文件,则DBWR触发LGWR进程并等待LRWR写完以后才会继续;3)重做条目的数量达到整个日志缓冲区的1/3时,触发LGWR;4)重做条目的数量达到1MB时,触发LGWR。 而触发同步写的条件就一个:当用户提交(commit)时,触发LGWR。null假如DBWR在写脏数据块的过程中,突然发生实例崩溃。我们已经知道,用户提交时,oracle是不一定会把提交的数据块写入数据文件的。那么实例崩溃时,必然会有一些已经提交但是还没有被写入数据文件的内存数据块丢失了。当实例再次启动时,oracle需要利用日志文件中记录的重做条目在buffer cache中重新构造出被丢失的数据块,从而完成前滚和回滚的工作,并将丢失的数据块找回来。于是这里就存在一个问题,就是oracle在日志文件中找重做条目时,到底应该找哪些重做条目?换句话说,应该在日志文件中从哪个起点开始往后应用重做条目?注意,这里所指的日志文件可能不止一个日志文件。 因为oracle需要随时预防可能的实例崩溃现象,所以oracle在数据库的正常运行过程中,会不断的定位这个起点,以便在不可预期的实例崩溃中能够最有效的保护并恢复数据。同时,这个起点的选择非常有讲究。首先,这个起点不能太靠前,太靠前意味着要处理很多的重做条目,这样会导致实例再次启动时所进行的恢复的时间太长;其次,这个起点也不能太靠后,太靠后说明只有很少的脏数据块没有被写入数据文件,也就是说前面已经有很多脏数据块被写入了数据文件,那也就意味着只有在DBWR启动的很频繁的情况下,才能使得buffer cache中所残留的脏数据块的数量很少。但很明显,DBWR启动的越频繁,那么所占用的写数据文件的I/O就越严重,那么留给其他操作(比如读取buffer cache中不存在的数据块等)的I/O资源就越少。这显然也是不合理的。null从这里也可以看出,这个起点实际上说明了,在日志文件中位于这个起点之前的重做条目所对应的在buffer cache中的脏数据块已经被写入了数据文件,从而在实例崩溃以后的恢复中不需要去考虑。而这个起点以后的重做条目所对应的脏数据块实际还没有被写入数据文件,如果在实例崩溃以后的恢复中,需要从这个起点开始往后,依次取出日志文件中的重做条目进行恢复。考虑到目前的内存容量越来越大,buffer cache也越来越大,buffer cache中包含几百万个内存数据块也是很正常的现象的前提下,如何才能最有效的来定位这个起点呢?null为了能够最佳的确定这个起点,oracle引入了名为CKPT的后台进程,通常也叫作检查点进程(checkpoint process)。这个进程与DBWR共同合作,从而确定这个起点。同时,这个起点也有一个专门的名字,叫做检查点位置(checkpoint position)。 oracle为了在检查点的算法上更加的具有可扩展性(也就是为了能够在巨大的buffer cache下依然有效工作),引入了检查点队列(checkpoint queue),该队列上串起来的都是脏数据块所对应的buffer header。而DBWR每次写脏数据块时,也是从检查点队列上扫描脏数据块,并将这些脏数据块实际写入数据文件的。当写完以后,DBWR会将这些已经写入数据文件的脏数据块从检查点队列上摘下来。这样即便是在巨大的buffer cache下工作,CKPT也能够快速的确定哪些脏数据块已经被写入了数据文件,而哪些还没有写入数据文件,显然,只要在检查点队列上的数据块都是还没有写入数据文件的脏数据块。而且,为了更加有效的处理单实例和多实例(RAC)环境下的表空间的检查点处理,比如将表空间设置为离线状态或者为热备份状态等,oracle还专门引入了文件队列(file queue)。null文件队列的原理与检查点队列是一样的,只不过每个数据文件会有一个文件队列,该数据文件所对应的脏数据块会被串在同一个文件队列上;同时为了能够尽量减少实例崩溃后恢复的时间,oracle还引入了增量检查点(incremental checkpoint),从而增加了检查点启动的次数。如果每次检查点启动的间隔时间过长的话,再加上内存很大,可能会使得恢复的时间过长。因为前一次检查点启动以后,标识出了这个起点。然后在第二次检查点启动的过程中,DBWR可能已经将很多脏数据块已经写入了数据文件,而假如在第二次检查点启动之前发生实例崩溃,导致在日志文件中,所标识的起点仍然是上一次检查点启动时所标识的,导致oracle不知道这个起点以后的很多重做条目所对应的脏数据块实际上已经写入了数据文件,从而使得oracle在实例恢复时再次重复的处理一遍,效率低下,浪费时间。null上面说到了有关CKPT的两个重要的概念:检查点队列(包括文件队列)和增量检查点。检查点队列在我们上面转储出来的buffer header里可以看到,就是类似ckptq: [65abceb4,63bec66c]和fileq: [65abcfbc,63becd10]的结构,记录的同样都是指向前一个buffer header和指向后一个buffer header的指针。这个队列上面挂的也是脏数据块对应的buffer header链表,但是它与LRUW链表不同。检查点队列上的buffer header是按照数据块第一次被修改的时间的先后顺序来排列的。越早修改的数据块的buffer header排在越前面,同时如果一个数据块被修改了多次的话,在该链表上也只出现一次。而且,检查点队列上的buffer header还记录了脏数据块在第一次被修改时,所对应的重做条目在重做日志文件中的地址,也就是RBA(Redo Block Address)。同样在转储出来的buffer header中可以看到类似LRBA: [0xe9.229.0]的结构,这就是RBA,L表示Low,也就是第一次被修改的时候的RBA。但是注意,在检查点队列上的buffer header,并不表示一定会有一个对应的RBA,比如控制文件重做(controlfile redo)就不会有相应的RBA。对于没有对应RBA的buffer header来说,在检查点队列上始终处于最尾端,其优先级永远比有RBA的脏数据块的buffer header要低。8i以前,每个working set都有一个检查点队列以及多个文件队列(因为一个数据文件对应一个文件队列);而从8i开始,每个working set都有两个检查点队列,每个检查点都会由checkpoint queue latch来保护。null增量检查点是从8i开始出现的,是相对于8i之前的完全检查点(complete checkpoint)而言的。完全检查点启动时,会标识出buffer cache中所有的脏数据块,然后启动DBWR进程将这些脏数据块写入数据文件。8i之前,日志切换的时候会触发完全检查点。而到了8i及以后,完全检查点只有在两种情况下才会被触发:1)发出命令:alter system checkpoint;2)除了shutdown abort以外的正常关闭数据库。注意,这个时候,日志切换不会触发完全检查点,而是触发增量检查点。8i所引入的增量检查点每隔三秒钟或发生日志切换时启动。它启动时只做一件事情:找出当前检查点队列上的第一个buffer header,并将该buffer header中所记录的LRBA(这个LRBA也就是checkpoint position了)记录到控制文件中去。如果是由日志切换所引起的增量检查点,则还会将checkpoint position记录到每个数据文件头中。也就是说,如果这个时候发生实例崩溃,oracle在下次启动时,就会到控制文件中找到这个checkpoint position作为在日志文件中的起点,然后从这个起点开始向后,依次取出每个重做条目进行处理。null上面所描述的概念,用一句话来概括,其实就是DBWR负责写检查点队列上的脏数据块,而CKPT负责记录当前检查点队列的第一个数据块所对应的的重做条目在日志文件中的地址。从这个意义上说,检查点队列比LRUW还要重要,LRUW主要就是区分出哪些数据块是脏的,不可以被重用的。而到底应该写哪些脏数据块,写多少脏数据块,则还是要到检查点队列上才能确定的。null我们用一个简单的例子来描述这个过程。假设系统中发生了一系列的事务,导致日志文件如下所示: 这时,对应的检查点队列则类似如下图六所示。我们可以看到,T1事务最先发生,所以位于检查点队列的首端,而事务T123最后发生,所以位于靠近尾端的地方。null同时,可以看到事务T1和T5都更新了7号数据文件的623号数据块。而在检查点队列上只会记录该数据块的第一次被更新时的RBA,也就是事务T1对应的RBA102,而事务T5对应的RBA105并不会被记录。因为根本就不需要在检查点队列上记录。当DBWR写数据块的时候,在写RBA102时,自然就把RBA105所修改的内容写入数据文件了。日志文件中所记录的提交标记也不会体现在检查点队列上,因为提交本身只是一个标记而已,不会涉及到修改数据块。null这时,假设发生三秒钟超时,于是增量检查点启动。增量检查点会将检查点队列的第一个脏数据块所对应的RBA记录到控制文件中去。在这里,也就是RBA101会作为checkpoint position记录到控制文件中。  然后,DBWR后台进程被某种条件触发而启动。DBWR根据一系列参数及规则,计算出应该写的脏数据块的数量,从而将RBA101到RBA107之间的这5个脏数据块写入数据文件,并在写完以后将这5个脏数据块从检查点队列上摘除,而留下了4个脏数据块在检查点队列上。如果在写这5个脏数据块的过程中发生实例崩溃,则下次实例启动时,oracle会从RBA101开始应用日志文件中的重做条目。null而在9i以后,在DBWR写完这5个脏数据块以后,还会在日志文件中记录所写的脏数据块的块号。如下图所示。这主要是为了在恢复时加快恢复的速度。这时,又发生三秒钟超时,于是增量检查点启动。这时它发现checkpoint position为RBA109,于是将RBA109写入控制文件。如果接着发生实例崩溃,则oracle在下次启动时,就会从RBA109开始应用日志.nullbuffer cache的优化 buffer cache的设置优化 buffer cache的设置随着oracle版本的升级而不断变化。 8i下使用db_block_buffers来设置,该参数表示buffer cache中所能够包含的内存数据块的个数; 9i以后使用db_cache_size来设置,该参数表示buffer cache的总共的容量,可以用字节、K、M为单位来进行设置。 而到了10g以后则更加简单,甚至可以不用去单独设置buffer cache的大小。因为10g引入了ASMM(Automatic Shared Memory Management)这样一个可以进行自我调整的组件,该组件可以自动调整shared pool size、db cache size等SGA中的组件。 只需要设置sga_target参数,则其他组件就能够根据系统的负载和历史信息自动的调整各个部分的大小。 要启动ASMM,只需要设置statistics_level为typical或all。nulloracle8.0以前只能设置一种buffer cache,而从8.0以后,oracle提供了三种类型的buffer cache,分别是default、keep、recyle。 keep和recycle是可选的,default必须存在。 8i以前使用db_block_buffer设置default、buffer_pool_keep设置keep、buffer_pool_recycle设置recyle。 而8i以后使用db_cache_size设置default、db_keep_cache_size设置keep、db_recycle_cache_size设置recycle。 10g不能自动设置db_keep_cache_size和db_recycle_cache_size,必须手工设置。 同时,8i以前,这三种buffer cache是独立指定的,互不制约。 而8i以后,这三种buffer cache是有相互制约关系的。如果指定了keep和recycle的buffer cache,则default类型的buffer cache的大小就是db_cache_size - buffer_pool_keep - buffer_pool_recycle。null通常将经常访问的对象放入keep类型的buffer cache里,而将不常访问的大表放入recycle类型的buffer cache里。其他没有指定buffer cache类型的对象都将进入default类型的buffer cache里。为对象指定buffer cache类型的方法如下: SQL> create table test (n number) storage (buffer_pool keep); SQL> alter table test
/
本文档为【20-buffer cache深度分析】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索