ITPub博客

首页 > Linux操作系统 > Linux操作系统 > 转载深入Shared Pool

转载深入Shared Pool

原创 Linux操作系统 作者:yyp2009 时间:2008-11-12 17:25:19 0 删除 编辑
深入Shared Pool 

Oracle数据库作为一个管理数据的产品,必须能够认出用户所提交的管理命令(通常叫做SQL语句),从而进行响应。认出的过程叫做解析SQL语句的过程,响应的过程叫做执行SQL语句的过程。解析是一个相当复杂的过程,它要考虑各种可能的异常情况,比如SQL语句涉及的对象不存在、提交的用户没有权限等。而且,还需要考虑如何执行SQL语句,采用什么方式去获取数据等。解析的最终结果是要产生Oracle自己内部的执行计划,从而指导SQL的执行过程。可以看到,解析是一个非常消耗资源的过程。因此,Oracle在解析用户提交的SQL语句的过程中,如果对每次出现的新的SQL语句都按照标准过程完整地从头到尾解析一遍的话,效率太低。尤其随着并发用户数量的增加、数据量的增加,数据库的整体性能将直线下降。

Oracle对SQL语句进行了概括和抽象,将SQL语句提炼为两部分。一部分是SQL语句的静态部分,也就是SQL语句本身的关键词、所涉及的表名称以及表的列名等。另一部分是SQL语句的动态部分,也就是SQL语句中的字面值(literal value,即有关表里的数据部分,比如where name='hsj'中,hsj就是SQL语句中的字面值,而where name就是SQL语句中的静态部分)。很明显,整个数据库中所包含的对象数量是有限的,而表中所包含的数据则是无限的。而正是这无限的数据导致了SQL语句的千变万化,也就是说,在数据库处理的所有SQL语句中,静态部分可以认为数量是有限的,而动态部分则是无限的。而实际上,动态部分对解析的影响相比于静态部分对解析的影响来说是微乎其微,也就是说通常情况下,对于相同的静态部分的SQL语句来说,不同的动态部分所产生的解析结果(执行计划)基本都是一样的(除非表里的数据分布极其不均匀,则有可能导致不同的动态部分产生不同的执行计划)。这也就为Oracle提高解析SQL语句的效率提供了方向。

Oracle会将用户提交来的SQL语句都缓存在内存中。每次处理新的一条SQL语句时,都会先在内存中查看是否有相同的SQL语句。如果相同则可以减少最重要的解析工作(也就是生成执行计划),从而节省了大量的CPU资源;反之,如果没有找到相同的SQL语句,则必须重新从头到尾进行完整的解析。这部分存放SQL语句的内存就叫做共享池。当然,shared pool里不仅仅是SQL语句,还包括执行计划、PL/SQL代码、PL/SQL程序的机器码、管理shared pool的内存结构、控制信息等内容。

如果SQL语句使用了绑定变量(bind variable),也就是用一个变量来替代SQL中的字面值。那么Oracle在shared pool中查找到相同的SQL语句的概率相对就很大。比如:

select c1 from t1 where c2=1;

select c1 from t1 where c2=2;

select c1 from t1 where c2=3;

这里的1、2、3就是SQL中的字面值。如果写成:

select c1 from t1 where c2=:v1;

这里的v1就是绑定变量,用来替代上面的1、2、3。解析时使用绑定变量,而在具体执行SQL时,才将字面值传入。这时,解析就是比较SQL语句的静态部分。前面我们已经知道,静态部分是有限的,很容易就能够缓存在内存里,从而找到相同的SQL语句的概率很高。如果没有使用绑定变量,则就是比较SQL语句的静态部分和动态部分,而动态部分的变化是无限的,因此这样的SQL语句很难被缓存在shared pool里。毕竟内存是有限的,不可能把所有的动态部分都缓存在shared pool里。不使用绑定变量导致的直接结果就是,找到相同的SQL语句的概率较低,导致必须完整地解析SQL语句,也就导致消耗更多的资源。从这里也可以看出,只有我们使用了绑定变量,才真正遵循了Oracle引入shared pool的根本思路,才能够更有效地利用shared pool。

shared pool的大小由初始化参数shared_pool_size决定。Oracle 10g以后可以不用设定该参数,而只需要指定sga_target,从而由Oracle自动决定shared pool的大小尺寸。

在一个很高的层次上来看,shared pool可以分为库缓存(library cache)和数据字典缓存(dictionary cache)。Library cache存放了最近执行的SQL语句、存储过程、函数、解析树以及执行计划等。而dictionary cache则存放了在执行SQL语句过程中,所参照的数据字典的信息,包括SQL语句所涉及的表名、表的列、权限信息等。dictionary cache里面的信息都是以数据行的形式存放的,而不是以数据块的形式存放的,因此也叫做row cache。对于dictionary cache来说,Oracle倾向于将它们一直缓存在shared pool里,不会将它们交换出内存,因此我们不用对它们进行过多的关注。而library cache则是shared pool里最重要的部分,也是在shared pool中进进出出最活跃的部分,需要我们仔细研究。所以,我们在说到shared pool实际上就可以认为是在指library cache。

5.2.1  shared pool的内存结构

从一个物理的层面来看,shared pool由许多内存块组成,这些内存块通常称为chunk。chunk是shared pool中内存分配的最小单位,一个chunk中的所有内存都是连续的。

当chunk属于可用类型的时候,它既不属于library cache,也不属于dictionary cache。如果该chunk被用于存放SQL相关的数据时,则该chunk就属于library cache;同样,如果该chunk被用于存放数据字典的信息时,则该chunk就属于dictionary cache。

这些chunk可以分为四类,这四类可以从x$ksmsp(该视图中的每条记录都表示当前在shared pool里的一个chunk)的ksmchcls字段看到。

ž free:这种类型的chunk不包含有效的对象,可以不受限制地被分配。

ž recr:意味着recreatable,这种类型的chunk里包含的对象可以在需要的时候被临时移走,并且在需要的时候重新创建。比如对于很多有关共享SQL语句的chunk就是recreatable的。

ž freeabl:这种类型的chunk包含的对象都是曾经被session使用过的,并且随后会被完全或部分释放。这种类型的chunk不能临时从内存移走,因为它们是在处理过程中间产生的,如果移走的话就无法被重建。

ž perm:意味着permanent,这种类型的chunk包含永久的对象,大型的permanent类型的chunk也可能含有可用空间,这部分可用空间可以在需要的时候释放回shared pool里。

在shared pool里,可用的chunk(free类型)会被串起来成为可用链表(free list)或者也可以叫做bucket(一个可用链表也就是一个bucket)。其结构如图5-1所示:

从图5-1中可以看到,可用的chunk链表(也就是bucket)被分成了254个,每个bucket上挂的chunk的尺寸是不一样的,有一个递增的趋势。同时每个bucket都有一个size字段,这个size就说明了该bucket上所能链接的可用chunk的大小尺寸。

图5-1  shared pool可用内存结构图

当某个进程需要shared pool里的一个chunk时,则该进程首先到符合所需空间大小的bucket上去扫描,以找到一个尺寸最合适的chunk,扫描持续到bucket的最末端,直到找到完全符合尺寸的chunk为止。如果找到的chunk的尺寸比需要的尺寸要大,则该chunk就会被拆分成两个chunk,一个chunk被用来存放数据,而另外一个则成为free类型的chunk,并被挂到当前该bucket上。

然而,如果该bucket上不含有任何需要尺寸的chunk,那么就从下一个非空的bucket上获得一个最小的chunk。如果在剩下的所有bucket上都找不到可用的chunk,则需要扫描已经使用的recreatable类型的chunk链表,从该链表上释放一部分的chunk,因为只有recreatable类型的chunk才是可以被临时移出内存的。

当某个chunk正在被使用时(可能是用户正在使用,也可能是使用了dbms_shared_pool包将对象钉在shared pool里),该chunk是不能被移出内存的。比如某个SQL语句正在执行,那么该SQL语句所使用的chunk是不能被移出内存的,该SQL语句所引用的表、索引等对象所占用的chunk也是不能被移出内存的。当shared pool中无法找到足够大小的所需内存时,就会发出ORA-4031的错误消息。当出现4031错误的时候,我们查询v$sgastat里可用的shared pool空间时,可能会发现name为“free memory”的可用内存还足够大,但是为何还是会报4031错呢?事实上,在Oracle发出4031错之前,已经释放了不少recreatable类型的chunk了,因此会产生不少可用内存。但是这些可用chunk中,没有一个chunk能够以连续的物理内存提供所需要的内存空间,从而才会发出4031的错误。

对bucket的扫描、管理、分配chunk等这些操作都是在shared pool latch的保护下进行的。如果shared pool含有数量巨大的非常小的free类型的chunk,则在扫描bucket时,shared pool latch会被锁定很长的时间,这也是Oracle 8i以前的shared pool latch争用的主要原因。而如果增加shared pool尺寸的话,仅仅是延缓shared pool latch的争用,而到最后,就会因为小的free 类型的chunk的数量越来越多,争用也会越来越严重。而到了Oracle 9i以后,由于大大增加了可用chunk链表(也就是bucket)的数量,同时,每个bucket所管理的可用chunk的尺寸递增的幅度非常小,于是就可以有效地将可用的chunk都均匀分布在所有的bucket上。这样的结果就是每个bucket上所挂的free类型的chunk都不多,所以在查找可用chunk而持有shared pool latch的时间也可以缩短很多。

注 意

 
所谓latch,就是轻量级的锁。它是非常底层的对象,用来保护对某个内存块的并发访问。比如某个进程要往一个内存块里写数据,这时如果还有其他进程也要往该内存块里写数据的话,如果不控制并发性,则多个进程写入的数据会互相覆盖。因此,为了不让这种情况出现,Oracle引入了latch,每个进程往内存块里写数据之前必须获得latch,写完以后释放latch。而latch的获取是串行化的,一次只有一个进程能够获得latch。获取和释放latch的速度很快,因此叫轻量级锁。有关latch的详细解释,请参考第10章。

从一个逻辑层面来看,shared pool由library cache和dictionary cache组成。shared pool中组件之间的关系可以用图5-2来表示。

图5-2  shared pool逻辑结构

从上图中可以看到,当SQL语句(比如select object_id,object_name from sharedpool_test)进入library cache时,Oracle会到dictionary cache中去找与sharedpool_test表有关的数据字典信息,比如表名、表的列等,以及用户权限等信息。如果发现dictionary cache中没有这些信息,则会将system表空间里的数据字典信息调入buffer cache内存,读取内存数据块里的数据字典内容,然后将这些读取出来的数据字典内容按照行的形式放入dictionary cache里,从而构造出dc_tables之类的对象。然后,再从dictionary cache的行数据中取出有关的列信息放入library cache中。

对于非常大的对象,我们可以让Oracle为它们单独从保留区域里分配空间,而不是从这个可用的chunk链表中来分配空间。通过设置初始化参数shared_pool_reserved_size,决定这部分空间的大小尺寸,默认为shared_pool_size的5%。这块保留区域与正常chunk管理是完全分开的,小的chunk不会进入这块保留区域,而这块保留区域的可用chunk也不会挂在bucket上。这块保留区域的使用情况可以从视图v$shared_pool_reserved中看到,通常来说,该视图的request_misses字段显示了无法从保留区域里获得足够大的chunk的总次数,该字段应该尽量为0。

5.2.2  library cache和dictionary cache概述

library cache最主要的功能就是存放用户提交的SQL语句、SQL语句相关的解析树(解析树也就是对SQL语句中所涉及的所有对象的展现)、执行计划、用户提交的PL/SQL程序块(包括匿名程序块、存储过程、包、函数等)以及它们转换后能够被Oracle执行的代码等。为了对这些内存结构进行管理,library cache中还存放了很多控制结构,包括lock、pin、dependency table等。

library cache也存放了很多的数据库对象的信息,包括表、索引等。有关这些数据库对象的信息都是从dictionary cache中获得的。如果用户对library cache中的对象信息进行了修改,比如为表添加了一个列等,则这些修改会返回到dictionary cache中。

在library cache中存放的所有信息单元都叫做对象(object),这些对象可以分成两类:一类叫存储对象,也就是上面所说的数据库对象。它们是通过显式的SQL语句或PL/SQL程序创建出来的,如果要删除它们,也必须通过显式的SQL命令进行删除。这类对象包括表、视图、索引、包、函数等;另一类叫做过渡对象,也就是上面所说的用户提交的SQL语句或者提交的PL/SQL匿名程序块等。这些过渡对象是在执行SQL语句或PL/SQL程序的过程中产生的,并缓存在内存里。如果实例关闭则删除,或者由于内存不足而被交换出去,从而被删除。

当用户提交SQL语句或PL/SQL程序块到shared pool以后,会在library cache中生成一个可执行的对象,这个对象就叫做游标(cursor)。不要把这里的游标与标准SQL(ANSI SQL)的游标混淆起来了,标准SQL里的游标是指返回多条记录的SQL形式,需要定义、打开、关闭。下面所说到的游标如无特别说明,都是指library cache中的可执行的对象。游标是可以被所有进程共享的,也就是说如果100个进程都执行相同的SQL语句,那么这100个进程都可以共用该SQL语句所产生的游标,从而节省了内存。

每个游标都是由library cache中的两个或多个对象所体现的,至少两个对象。一个对象叫做父游标(parent cursor),包含游标的名称以及其他独立于提交用户的信息。从v$sqlarea视图里看到的都是有关父游标的信息;另外一个或多个对象叫做子游标(child cursor),如果SQL文本相同,但是可能提交SQL语句的用户不同,或者用户提交的SQL语句所涉及的对象为同名词等,都有可能生成不同的子游标。因为这些SQL语句的文本虽然完全一样,但是上下文环境却不一样,因此这样的SQL语句不是一个可执行的对象,必须细化为多个子游标后才能够执行。子游标含有执行计划或者PL/SQL对象的程序代码块等。

在介绍library cache的内部管理机制前,先简单介绍一下所谓的hash算法。

Oracle内部在实现管理的过程中大量用到了hash算法。hash算法是为了能够进行快速查找定位所使用一种技术。所谓hash算法,就是根据要查找的值,对该值运用一定的hash函数后得出该值所在的索引号。进入索引号所对应的一列数值列表(可以理解为一个二维数组)里,然后再对其中所含有的值进行逐个比较,从而找到该值。这样就避免了对整个数值列表进行扫描才能找到该值,这种全扫描的方式显然要比hash查找方式低效很多。其中,每个索引号对应的数值列在Oracle里都叫做一个hash bucket。

我们来列举一个最简单的hash算法。假设我们的数值列表最多可以有10个元素,也就是有10个hash bucket,每个bucket最多可以包含10个数值。则对应的二维数组就是t[10][10]。我们可以定义hash算法为n MOD 10。通过这种算法,可以将所有进入的数据均匀放在10个hash bucket里面,hash bucket编号从0到9。比如,我们把1到100都通过这个hash函数均匀放到这10个hash bucket里,当查找32在哪里时,只要将32 MOD 10等于2,这样就知道32必定位于2号hash bucket里,于是到t[2][10]里去找,2号hash bucket里有10个数值,逐个比较2号hash bucket里是否存在32就可以了。这里可以看出,为了找到32这个值,我们总共需要比较11次(1+10)。如果不使用hash算法,而采用遍历扫描的方式,则需要比较100次。

library cache就是使用多个hash bucket来管理的,其hash算法当然比我们前面列举的要复杂多了。每个hash bucket后面都串连着多个句柄(该句柄叫做library cache object handle),这些句柄描述了library cache里的对象的一些属性,包括名称、标记、指向对象所处的内存地址的指针等。实际上,hash bucket就是通过串连起来的对象句柄才体现出来的,它本身是一个逻辑上的概念,是一个逻辑组,而不像对象是一个具体的实体。Oracle根据shared_pool_size所指定的shared pool尺寸自动计算hash buckets的个数,shared pool越大,则可以挂载的对象句柄就越多。

可以使用图5-3来描述library cache的结构。

图5-3  library cache结构图

当一条SQL语句进入library cache的时候,先将SQL文本转化为对应ASCII数值,然后对这些ASCII数值进行hash函数的运算,传入函数的参数包括SQL语句的名称(name,对于SQL语句来说其name就是SQL语句的文本)以及命名空间(namespace,对于SQL语句来说是“SQL AREA”,表示共享游标。可以从视图v$librarycache里找到所有的namespace)。运用hash函数后得到一个值,该值就是hash bucket的号码,从而该SQL语句被分配到该号的hash bucket里去。

当某个进程需要处理某个对象时,比如处理一条新进入的SQL语句时,它会对该SQL语句应用hash函数算法,以决定其所在的hash bucket的编号,然后进入该hash bucket进行扫描以确定是否存在相同的SQL语句。有可能会发生该对象的句柄存在,但是句柄所指向的对象已经被交换出内存的情况出现。这时对应的对象必须被再次装载(reload)。也可能该对象的句柄都不存在,也就是说该条SQL语句是第一次被执行,这时进程必须重新构建一个对象句柄挂到hash bucket上,然后再重新装载对象。SQL语句相关的对象有很多(最直观的就是SQL语句的文本),这些对象都存放在library cache里,它们都通过句柄来访问。可以把library cache理解为一本书,而SQL语句的对象就是书中的页,而句柄就是目录,通过目录可以快速定位到指定内容的页。

对象句柄存放了对象的名称(name)、对象所属的命名空间(namespace)、有关对象的一些标记(比如对象是否为只读、为本地对象还是远程对象、是否被pin在内存中等)以及有关对象的一些统计信息等。其中存放的最重要的内容应该就是指向Heap 0对象的指针了。Heap 0用来存放与对象有直接关系的一些信息,比如对象类型、对象相关的表、实际的执行计划、执行PL/SQL的机器码等。Heap是由一个或多个chunk组成的,这些chunk可以是分散的分布在library cache中的,不需要连续分布。

我们可以通过查询视图v$db_object_cache来显示library cache中有哪些对象被缓存,以及这些对象的大小尺寸。比如,我们可以用下面的SQL语句来显示每个namespace中,大小尺寸排在前3名的对象:

select *

from (select row_number() over(partition by namespace order by sharable_mem desc) size_rank,

             namespace,

             sharable_mem,

             substr(name, 1, 50) name

        from v$db_object_cache

       order by sharable_mem desc)

where size_rank <= 3              

order by namespace, size_rank;

而dictionary cache则是专门用来存放SYS schema所拥有的对象的内存区域。使用dictionary cache时以行为单位,而不像其他比如buffer cache以数据块为单位,因此dictionary cache也叫做row cache。构造dictionary cache的目的是为了加快解析SQL语句的速度,因为dictionary cache里存放了所有表的定义、Storage信息、用户权限信息、约束定义、回滚段信息、表的统计信息等。基本上我们不需要过多地关注它。

Oracle里是没有初始化参数来控制library cache和dictionary cache应该占多大的内存的,我们只能控制shared pool的大小。因为前面说过,shared pool里的一个可用chunk,如果存放了数据字典的信息,那么它就属于dictionary cache。否则,如果存放了SQL文本或执行计划等信息,则它就属于library cache。有时某chunk原先可能放的是SQL文本,后来由于内存不足被数据字典信息所覆盖,则该chunk就从library cache变成了dictionary cache。所以我们不能单独控制library cache和dictionary cache各是多大。

5.2.3  解析SQL语句的过程

为了将用户写的SQL文本转化为Oracle认识的且可执行的语句,这个过程就叫做解析过程。解析分为硬解析和软解析。一条SQL语句在第一次被执行时必须进行硬解析。

当客户端发出一条SQL语句(也可以是一个存储过程或者一个匿名PL/SQL块)进入shared pool时(注意,我们从前面已经知道,Oracle对这些SQL不叫做SQL语句,而是称为游标。因为Oracle在处理SQL时,需要很多相关的辅助信息,这些辅助信息与SQL语句一起组成了游标),Oracle首先将SQL文本转化为ASCII值,然后根据hash函数计算其对应的hash值(hash_value)。根据计算出的hash值到library cache中找到对应的bucket,然后比较bucket里是否存在该SQL语句。

如果不存在,则需要按照我们前面所描述的,获得shared pool latch,然后在shared pool中的可用chunk链表(也就是bucket)上找到一个可用的chunk,之后释放shared pool latch。在获得了chunk以后,这块chunk就可以认为是进入了library cache。接下来,进行硬解析过程。硬解析包括以下几个步骤。

* 对SQL语句进行文法检查,看是否有文法错误。比如没有写from、select拼写错误等。如果存在文法错误,则退出解析过程。

* 到数据字典里校验SQL语句涉及的对象和列是否都存在。如果不存在,则退出解析过程。这个过程会加载dictionary cache。

* 将对象进行名称转换。比如将同名词翻译成实际的对象等。比如select * from t中,t是一个同名词,指向hr.t1,于是Oracle将t转换为hr.t1。如果转换失败,则退出解析过程。

* 检查发出SQL语句的用户是否具有访问SQL语句里所引用的对象的权限。如果没有权限,则退出解析过程。

* 通过优化器创建一个最优的执行计划。这个过程会根据数据字典里记录的对象的统计信息,来计算最优的执行计划。这一步牵涉大量数学运算,是最消耗CPU资源的。

 将该游标所产生的执行计划、SQL文本等装载进library cache的heap中。

在硬解析的过程中,进程会一直持有library cache latch,直到硬解析结束为止。硬解析结束以后,会为SQL语句产生两个游标,一个是父游标,另一个是子游标。父游标里主要包含两种信息:SQL文本以及优化目标(optimizer goal)。父游标在第一次打开时被锁定,直到其他所有的session都关闭该游标后才被解锁。当父游标被锁定的时候是不能被交换出library cache的,只有在解锁以后才能被交换出library cache。父游标被交换出内存时,父游标对应的所有子游标也被交换出library cache。子游标包括游标所有的信息,比如具体的执行计划、绑定变量等。子游标随时可以被交换出library cache,当子游标被交换出library cache时,Oracle可以利用父游标的信息重新构建出一个子游标来,这个过程叫reload。可以使用下面的方式来确定reload的比率:

select 100*sum(reloads)/sum(pins) Reload_Ratio from v$librarycache;

一个父游标可以对应多个子游标。子游标具体的个数可以从视图v$sqlarea的version_count字段体现出来。而每个具体的子游标则全都在视图v$sql里体现。当具体绑定变量的值与上次绑定变量的值有较大差异(比如上次执行的绑定变量值的长度是6位,而这次执行绑定变量的值的长度是200位)时或者当SQL语句完全相同,但是所引用的表属于不同的用户时,都会创建一个新的子游标。

如果在bucket中找到了该SQL语句,则说明该SQL语句以前运行过,于是进行软解析。软解析是相对于硬解析而言的,如果解析过程中,可以从硬解析的步骤中去掉一个或多个的话,这样的解析就是软解析。软解析分为以下三种类型。

ž 第一种是某个session发出的SQL语句与library cache里其他session发出的SQL语句一致。这时,该解析过程中可以去掉硬解析中的*,但是仍然要进行硬解析过程中的***,也就是表名和列名检查、名称转换和权限检查。

ž 第二种是某个session发出的SQL语句是该session之前发出的曾经执行过的SQL语句。这时,该解析过程中可以去掉硬解析中的***这四步,但是仍然要进行权限检查,因为可能通过grant改变了该session用户的权限。

ž 第三种是当设置了初始化参数session_cached_cursors时,当某个session第三次执行相同的SQL时,则会把该SQL语句的游标信息转移到该session的PGA里。这样,该session以后再执行相同的SQL语句时,会直接从PGA里取出执行计划,从而跳过硬解析的所有步骤。这种情况下,是最高效的解析方式,但是会消耗很大的内存。

我们举一个例子来说明解析SQL语句的过程。在该测试中,绑定变量名称相同,但是变量类型不同时,所出现的解析情况。如下所示。

首先,执行下面的命令,清空shared pool里所有的SQL语句:

SQL> alter system flush shared_pool;

然后,定义一个数值型绑定变量,并为该绑定变数赋一个数值型的值以后,执行具体的查询语句。

SQL> variable v_obj_id number;

SQL> exec :v_obj_id := 4474;

SQL> select object_id,object_name from sharedpool_test where object_id=:v_obj_id;

     OBJECT_ID        OBJECT_NAME

     ----------       ---------------------------

     4474               AGGXMLIMP

接下来,定义一个字符型的绑定变量,变量名与前面相同,为该绑定变数赋一个字符型的值以后,执行相同的查询:

SQL> variable v_obj_id varchar2(10);

SQL> exec :v_obj_id := '4474';

SQL> select object_id,object_name from sharedpool_test where object_id=:v_obj_id;

     OBJECT_ID        OBJECT_NAME

     ----------       ---------------------------

     4474               AGGXMLIMP

然后我们到视图v$sqlarea里找到该SQL的父游标的信息,并到视图v$sql里找该SQL的所有子游标的信息。

SQL> select sql_text,version_count from v$sqlarea where sql_text like ‘%sharedpool_test%’;

SQL_TEXT                                                                          VERSION_COUNT           

------------------------------------------------------------------------------------

select object_id,object_name from sharedpool_test where object_id=:v_obj_id         2

SQL> select sql_text,child_address,address from v$sql where sql_text like ‘%sharedpool_test%’;

SQL_TEXT                                                                          CHILD_ADDRESS  ADDRESS

------------------------------------------------------------------- ----------

select object_id,object_name from sharedpool_test where object_id=:v_obj_id   6757F358

         676B6D08

select object_id,object_name from sharedpool_test where object_id=:v_obj_id   674440FC

   676B6D08

从记录父游标的视图v$sqlarea的version_count列可以看到,该SQL语句有2个子游标。而从记录子游标的视图v$sql里可以看到,该SQL文本确实有两条记录,而且它们的SQL文本所处的地址(ADDRESS列)也是一样的,但是子地址(CHILD_ADDRESS)却不一样。这里的子地址实际就是子游标所对应的heap 0的句柄。

由此我们也可以看到,存在许多因素可能导致SQL语句不能共享。常见的因素包括SQL文本大小写不一致、SQL语句的绑定变量的类型不一致、SQL语句涉及的对象名称虽然一致但是位于不同的schema下、SQL的优化器模式不一致(比如添加提示、修改了optimizer_mode参数等)等。

5.2.4  设置shared pool

对于设置shared pool的大小来说,没有一个通用的、普遍适用的值,不同的系统负载需要不同大小的shared pool来管理。通常我们在设置shared pool时,应该遵循“不要太大、也不要太小”的原则,设置一个初始的值,然后让系统正常运行一段时间,在这段时间里,对shared pool的使用情况进行观察监控,最后根据系统的负载得出一个在当前负载下比较合理的值。注意,这里只是说明是在当前负载下,如果随着系统的不断升级,导致负载发生一个比较大的变化,这时又需要对shared pool重新监控并做出相应的调整了。

一般来说,设置1GB以上的shared pool不会给性能带来明显的提高,相反,这将给Oracle管理shared pool以及监控shared pool的过程中会带来较多的麻烦。我们可以在系统上线时,设置shared pool为SGA的10%,但是不要超过1GB,让系统正常运行一段时间,我们可以借助Oracle 9i以后所引入的顾问(advisory)来帮助我们判断shared pool的设置是否合理。

只要将初始化参数statistics_level设置为typical(默认值)或all,就能启动对shared pool的建议功能,如果设置为basic,则关闭建议功能。使用如下的SQL语句显示Oracle所建议的shared pool的大小。

SQL> SELECT shared_pool_size_for_estimate “SP”, estd_lc_size “EL”, estd_lc_memory_objects “ELM”,

  2  estd_lc_time_saved “ELT”, estd_lc_time_saved_factor as “ELTS”,

  3  estd_lc_memory_object_hits as “ELMO”

  4  FROM v$shared_pool_advice;

  SP          EL                ELM              ELT            ELTS           ELMO

---------  -------        ----------     ---------      --------     ------------

  128        135               12223             8566          0.9993         2980874

  160        166               15809             8567          0.9994         2981291

  192        197               19167             8570          0.9998         2982322

  224        228               22719             8572               1         2982859

  256        259               27594             8572               1         2982906

  288        292               31436             8572               1         2982917

  320        323               36157             8572               1         2982920

  352        354               40371             8572               1         2982929

  384        385               45019             8572               1         2982937

  416        389               46099             8572               1         2982937

  448        389               46099             8572               1         2982937

  480        389               46099             8572               1         2982937

  512        389               46099             8572               1         2982937

第一列表示Oracle所估计的shared pool的尺寸值,其他列表示在该估计的shared pool大小下所表现出来的指标值,具体含义可以参见Oracle的联机帮助。我们主要关注estd_lc_time_saved_factor列的值,当该列值为1时,表示再增加shared pool的大小对性能的提高没有意义。对于上例来说,当shared pool为224MB时,达到最佳大小。对于设置比224MB更大的shared pool来说,就是浪费空间,没有更多的好处了。

我们还可以借助v$shared_pool_advice来观察在不同的shared pool尺寸情况下的响应时间(单位是秒)各是多少,如下所示。

SQL> SELECT 'Shared Pool' component,

  2         shared_pool_size_for_estimate estd_sp_size,

  3         estd_lc_time_saved_factor parse_time_factor,

  4         CASE

  5           WHEN current_parse_time_elapsed_s + adjustment_s < 0 THEN

  6            0

  7           ELSE

  8            current_parse_time_elapsed_s + adjustment_s

  9         END response_time

 10    FROM (SELECT shared_pool_size_for_estimate,

 11                 shared_pool_size_factor,

 12                 estd_lc_time_saved_factor,

 13                 a.estd_lc_time_saved,

 14                 e.VALUE / 100 current_parse_time_elapsed_s,

 15                 c.estd_lc_time_saved - a.estd_lc_time_saved adjustment_s

 16            FROM v$shared_pool_advice a,

 17             (SELECT * FROM v$sysstat WHERE NAME = 'parse time elapsed') e,

 18                 (SELECT estd_lc_time_saved

 19                    FROM v$shared_pool_advice

 20                   WHERE shared_pool_size_factor = 1) c);

COMPONENT   ESTD_SP_SIZE PARSE_TIME_FACTOR RESPONSE_TIME

----------- ------------ ----------------- -------------

Shared Pool          128            0.9993        252.82

Shared Pool          160            0.9994        251.82

Shared Pool          192            0.9998        248.82

Shared Pool          224                  1        246.82

Shared Pool          256                  1        246.82

Shared Pool          288                  1        246.82

Shared Pool          320                  1        246.82

Shared Pool          352                  1        246.82

Shared Pool          384                  1        246.82

Shared Pool          416                  1        246.82

Shared Pool          448                  1        246.82

Shared Pool          480                  1        246.82

Shared Pool          512                  1        246.82

如果是Oracle 9i之前的版本,没有顾问的话,则可以在系统运行过程中,观察shared pool的统计信息以及等待事件来判断shared pool是否合理。

如果设置了共享服务器(Shared Server)的连接模式,则注意要配置large pool(通过设置large_pool参数)。如果不设置large pool,session的PGA会有一部分在shared pool里进行分配,从而加重shared pool的负担。

深入Log Buffer 

数据库在运行过程中,不可避免地要遇到各种能够导致数据库损坏的情况。比如突然断电、Oracle或者操作系统的程序bug导致数据库内部逻辑结构损坏、磁盘介质损坏等,都有可能造成数据库崩溃,从而导致数据丢失的现象发生。

为了避免,或者说为了修复这些状况所导致的数据丢失现象,Oracle引入了日志缓冲区和日志文件的概念。所谓日志,就是将数据库中所有改变数据块的操作,都原原本本地记录下来。这些改变数据块的操作不仅包括对数据表的DML命令或者引起数据字典内容变化的DDL命令,还包括对索引的改变、对回滚段数据块的改变等。只有将数据库中所有的变化都记录下来,当发生数据库损坏时,才能够通过重新应用这些变化,从而达到恢复数据库的目的。

既然是要记录,那就必然引出一个问题,就是如何记录这些变化?比较容易想到的有两种方式。

第一种是使用逻辑的记录方式,也就是用描述性的语句来记录整个变化过程。比如对于某个update更新操作来说来说,可以记录为两条语句:delete 旧值以及insert 新值。这种方式的优点是非常节省空间,因为对每个操作,只需要记录几条逻辑上的语句即可。但是缺点也很明显,就是一旦需要进行恢复,就会非常消耗资源。设想一下,某个update操作更新了非常多的数据块,由于buffer cache内存有限,很多脏数据块都已经写入了数据文件。但就在更新快结束时,突然发生断电,所做的更新丢失。那么重新启动实例时,Oracle需要应用日志文件里的记录,于是重新发出delete旧值以及insert新值的语句。这个过程需要重新查找数据文件中符合条件的数据块,然后再挑出来进行更新。这个过程将非常消耗时间,而且会占用大量的buffer cache。

第二种方式是使用物理的记录方式,也就是将每个数据块改变前的镜像和改变后的镜像都记录下来。这种方式优点就是恢复起来速度非常快,直接根据日志文件里所记录的数据块地址和内容更新数据文件中对应的数据块。但是缺点也很明显,就是非常占用磁盘空间。

而Oracle在记录日志的方式上,采用了逻辑和物理相结合的方式。也就是说,Oracle针对每个数据块,记录了插入某个值或者删除某个值的描述语句。假如某个update更新了100个数据块,则Oracle会针对每个数据块记录一对delete 旧值和insert 新值的语句,共有100对这样的描述语句。在每一对描述语句中,都记录了相关数据块的物理地址。通过这种逻辑与物理相结合的方式,Oracle在记录变化时能够尽量节省空间,同时在应用变化时,又能比较快速。

为了临时存放所产生的日志信息,Oracle在SGA中开辟了一块内存区域。这块区域就叫做日志缓冲区(log buffer),当满足一定条件以后,Oracle会使用名为LGWR的后台进程将log buffer中的日志信息写入联机日志文件里。

可以使用初始化参数log_buffer来设置日志缓冲区的大小,单位是字节。日志缓冲区会进一步细分为多个块,每个块的尺寸与操作系统的一个块的尺寸相同,基本都是512字节。我们可以用如下方式来获得日志缓冲区的块尺寸。

SQL> select distinct lebsz as redo_block_size from x$kccle;

REDO_BLOCK_SIZE

---------------

512

也可以用下面的方式来计算出日志缓冲区的块尺寸。

SQL> select round((a.redosize+b.redowast)/c.redoblks) + 16 as redo_block_size from

  2  (select value redosize from v$sysstat where name='redo size') a,

  3  (select value redowast from v$sysstat where name='redo wastage') b,

  4  (select value redoblks from v$sysstat where name='redo blocks written') c;

REDO_BLOCK_SIZE

---------------

512

日志缓冲区只是日志信息临时存放的区域,这块区域是有限的,而且其中的每个块都是能够循环使用的。这也就说明,日志缓冲区中的内容必须要写入磁盘的文件里,才能永久保留下来,才能在数据库崩溃时能够用来进行恢复。这个文件就叫做联机日志文件。在每个日志缓冲区中的日志块被重用之前,其内容必然已经被写入了磁盘上的联机日志文件中。

联机日志文件就是日志缓冲区的完全副本,组成日志文件的每个日志块的内容都来自于日志缓冲区的日志块。每个日志缓冲区中的日志块都对应到日志文件中的一个日志块。日志缓冲区中的日志块按照发生的先后顺序,放入联机日志文件。

由于日志文件在故障恢复中的重要性,建议至少使用两个日志文件组成一个日志文件组。同一个日志文件组中的日志文件内容一模一样,因为日志缓冲区中的日志块同时会写入日志文件组中的每个日志文件中。每个数据库都必须至少拥有两个日志文件组。这是由于只要数据库一天不停止运行,就会不断产生日志信息,就会不断写入联机日志文件,联机日志文件总会有写满的时候。我们不可能让联机日志文件无限大,也不可能放无限多的联机日志文件,所以联机日志文件必须是循环使用的,在若干个日志文件中轮流的进行写入。一个日志文件写满以后转换到另外一个日志文件继续写的过程叫做日志切换(log switch)。

当一个联机日志文件写满时,可以选择将其归档为脱机日志文件,通常叫做归档日志文件。归档也就是副本,归档的过程也就是将写满的联机日志文件复制到预先指定的目录的过程。只有当一个联机日志文件完成归档以后,该联机日志文件才能够被再次循环使用。强烈建议在生产库中选择这种归档方式,只有在测试环境中可以选择不归档。

可以说,日志缓冲区和日志文件存在的唯一目的就是为了保证被修改的数据不会被丢失。反过来说,也就是为了能够在数据库崩溃的时候,可以用来将数据库恢复到崩溃的那个时间点上。这也就是说,只有将被修改的数据块的日志信息写入了联机日志文件以后,该被修改的数据块才可以说是安全的。如果日志信息在没有被写入日志文件时发生实例崩溃,这时对数据的修改仍将丢失。由此我们可以看出,将日志缓冲区中的日志信息写入日志文件是一个非常重要的过程,这个过程是由一个名为LGWR的后台进程完成的。LGWR 承担了维护系统数据完整性的任务,它保证了数据在任何情况下都不会丢失。

触发LGWR进程将日志缓冲区中的日志信息写入联机日志文件条件包括以下几种。

ž 前台进程触发,包括两种情况。最显而易见的一种情况就是用户发出commit或rollback语句进行提交时,需要触发LGWR将内存里的日志信息写入联机日志文件,因为提交的数据必须被保护而不被丢失;另外一种情况就是在日志缓冲区中找不到足够的内存来放日志信息时,也会触发LGWR进程将一些日志信息写入联机日志文件以后,从而释放一些空间。

ž 每隔三秒钟,LGWR启动一次。

ž 在DBWn启动时,如果发现脏数据块所对应的重做条目还没有写入联机日志文件,则DBWn触发LGWR进程并等待LRWR写完以后才会继续。

ž 日志信息的数量达到整个日志缓冲区的1/3时,触发LGWR。

ž 日志信息的数量达到1MB时,触发LGWR。

5.3.1  log buffer的内存结构

我们已经知道,日志缓冲区用来存放事务对数据块的变化的日志信息。那么这里的日志信息到底包含哪些内容,是由哪些结构组成的呢?

Oracle记录数据库变化(也就是记录日志信息)的最小单位是改动向量(change vector)。改动向量用来描述对数据库中任何单个数据块所做的一次改动。改动向量的内容包括被改动的数据块的版本号、事务操作代码、被改动的数据块的地址等。这里的版本号非常重要,它能够帮助数据块始终能够体现当前最新的状态。Oracle在建立改动向量时,会从数据块中复制其版本号。而当恢复期间,Oracle读取改动向量并将改动应用于相应的数据块以后,被恢复的数据块的版本号加1。这里的数据块可以属于表或数据索引,也可以属于回滚段。但是对于临时表空间里的临时段,不会生成改动向量。

当多个改动向量按照先后顺序组合在一起,从而完成对数据库的一次改动时,Oracle称这组改动向量为重做记录(redo record)。重做记录用来描述对数据库的一个原子改动。所谓原子改动,就是说,当应用改动中的改动向量时,要么全部成功,要么全部失败,不存在部分成功部分失败的情况。重做记录能够帮助整个数据库体现当前最新的状态。

一个事务至少产生一个重做记录,也可能产生多个重做记录。而Oracle在应用日志记录进行恢复的过程中,以事务作为恢复的最小单位。要么恢复整个事务,要么回滚整个事务。也就是说,要么运用事务重做记录里的所有改动向量,要么一个改动向量都不运用。

因此,日志缓冲区就是许多重做记录按照发生的先后顺序组成的。同时,日志文件也就是由许多重做记录按照先后顺序排列在一起而组成的文件。

我们举个实例来说明重做记录和改动向量产生的过程。比如我们发出如下更新语句(假设表redo_test的NAME列上没有建立索引):

SQL> select * from redo_test;

ID     NAME

----- ----------

1      abc

2      abc

SQL> update redo_test set name='cdf' where id=1;

该语句发出以后,会产生一个重做记录,用来描述对表中数据块进行的修改。包括下面三个改动向量。

ž 对回滚段事务表的改动,这发生在回滚段段头。事务表中包含被修改的数据块的地址、该事务的状态(commit或active),以及存有该事务所使用的回滚段的地址。如果事务表被修改,就会产生针对它的改动向量。

ž 对回滚段数据块的改动。将修改前的旧值(abc)存放到回滚段的数据块里。这时回滚段发生改变,于是产生改动向量。

ž 对redo_test表的数据块所做的改动。将修改后的新值(cdf)存放到表的数据块里。这时数据块发生改变,于是产生改动向量。

从这个过程可以看到,对于这个update事务,重做记录中会有三个改动向量。当然可能还有其他情况会产生新的重做记录,比如修改的列如果有索引,则必须修改索引。这时就会产生第二个重做记录,用来描述对索引数据块的修改。这时候的重做记录还是和第一个重做记录一样,包含多个改动向量。此外,在事务完成之后运行commit或rollback语句时,就会产生第三个重做记录。该重做记录只有一个改动向量,用来记录对回滚段事务表的更改,因为commit或rollback时,需要更新事务表里记录的该事务的状态。

5.3.2  log buffer的内部管理机制 

日志缓冲区的内部管理分为两部分,一部分是生成重做记录,另一部分就是重做记录写入联机日志文件。这两部分不是孤立的,没有关联的。在生成重做记录的过程中,可能会触发LGWR将重做记录写入联机日志文件。

我们先用一个例子来说明在日志缓冲区中的操作过程,并使用[file# , blk#]来表示某个数据块;file#表示文件号;blk#表示数据块号。

假设session 1发出更新语句:update redo_test set name='cdf' where id=1;

Oracle首先找出id=1所在的数据块(假设为[file#4,blk#120])放入buffer cache,然后找出一个可用的回滚段数据块(假设为[file#2,blk#19]),将旧值'abc'放入该块,同时生成重做记录。然后将'cdf'放入表的数据块,再生成重做记录。这时日志缓冲区的结构可以简单地表示为下面的形式(我们在前面描述日志缓冲区的内存结构时,知道重做记录中最重要的就下面列的这几列内容。同时,下面的一行就表示一个重做记录):

行号     事务id   file#       block#  row     column      value

1       T1      2            19      -       -           abc

2       T1      4            120     1       2           cdf

这时假设session 2发出其他更新语句:update t set c1=10 where c1=9;

同样的道理,Oracle找到该数据块(假设为[file#5,blk#200])放入buffer cache,并找到回滚段数据块(假设为[file#2,blk#30])存放旧值,生成重做记录,更新表的数据块,再次生成重做记录。这时日志缓冲区的结构类似如下形式:

行号     事务id   file#       block#  row     column      value

1       T1      2            19      -       -           abc

2       T1      4            120     1       2           cdf

3       T20     2        30      -       -           9

4       T20     5        200     20      1           10

这时,session 1又发出更新语句:update redo_test set name='xyz1' where id=2,并提交(commit)。同样的方式处理回滚段和数据块,并生成重做记录。假设这时生成日志缓冲区为:

行号     事务id   file#       block#  row     column      value

1       T1      2            19      -      

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/13750068/viewspace-492471/,如需转载,请注明出处,否则将追究法律责任。

上一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2008-10-17

  • 博文量
    330
  • 访问量
    1024216