Shared pool深入分析及性能调整

Shared pool深入分析及性能调整(一)

  (2012-04-06 12:14:25)
标签: 

杂谈

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

    oracle对SQL语句进行了概括和抽象,将SQL语句提炼为两部分,一部分是SQL语句的静态部分,也就是SQL语句本身的关键词、所涉及的表名称以及表的列等。另一部分就是SQL语句的动态部分,也就是SQL语句中的值(即表里的数据)。很明显的,整个数据库中所包含的对象数量是有限的,而其中所包含的数据则是无限的。而正是这无限的数据导致了SQL语句的千变万化,也就是说在数据库运行的过程中,发生的所有SQL语句中,静态部分可以认为数量是有限的,而动态部分则是无限的。而实际上,动态部分对解析的影响相比静态部分对解析的影响来说是微乎其微,也就是说通常情况下,对于相同的静态部分的SQL语句来说,不同的动态部分所产生的解析结果(执行计划)基本都是一样的。这也就为oracle提高解析SQL语句的效率提供了方向。 
    oracle会将用户提交来的SQL语句都缓存在内存中。每次处理新的一条SQL语句时,都会先在内存中查看是否有相同的SQL语句。如果相同则可以减少最重要的解析工作(也就是生成执行计划),从而节省了大量的资源;反之,如果没有找到相同的SQL语句,则必须重新从头到尾进行完整的解析过程。这部分存放SQL语句的内存就叫做共享池(shared pool)。当然,shared pool里不仅仅是SQL语句,还包括管理shared pool的内存结构以及执行计划、控制信息等等内存结构。 

    当oracle在shared pool中查找相同的SQL语句的过程中,如果SQL语句使用了绑定变量(bind variable),那么就是比较SQL语句的静态部分,前面我们已经知道,静态部分是有限的,很容易就能够缓存在内存里,从而找到相同的SQL语句的概率很高。如果没有使用绑定变量,则就是比较SQL语句的静态部分和动态部分,而动态部分的变化是无限的,因此这样的SQL语句很难被缓存在shared pool里。毕竟内存是有限的,不可能把所有的动态部分都缓存在shared pool里,即便能够缓存,管理这样一个无限大的shared pool也是不可能完成的任务。不使用绑定变量导致的直接结果就是,找到相同的SQL语句的概率很低,导致必须完整的解析SQL语句,也就导致消耗更多的资源。从这里也可以看出,只有我们使用了绑定变量,才真正遵循了oracle引入shared pool的哲学思想,才能够更有效的利用shared pool。 

    shared pool的大小由初始化参数shared_pool_size决定。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。

2.shared pool的内存结构  
    从一个逻辑层面来看,shared pool由library cache和dictionary cache组成。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中。 

    从一个物理的层面来看,shared pool是由许多内存块组成,这些内存块通常称为chunk。Chunk是shared pool中内存分配的最小单位,一个chunk中的所有内存都是连续的。这些chunk可以分为四类,这四类可以从x$ksmsp(该视图中的每个行都表示shared pool里的一个chunk)的ksmchcls字段看到: 
1) free:这种类型的chunk不包含有效的对象,可以不受限制的被分配。 
2) recr:意味着recreatable,这种类型的chunks里包含的对象可以在需要的时候被临时移走,并且在需要的时候重新创建。比如对于很多有关共享SQL语句的chunks就是recreatable的。

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

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

    当chunk属于free类型的时候,它既不属于library cache,也不属于dictionary cache。如果该chunk被用于存放SQL游标时,则该chunk进入library cache;同样,如果该chunk被用于存放数据字典的信息时,则该chunk进入dictionary cache。 
在shared pool里,可用的chunk(free类型)会被串起来成为可用链表(free lists)或者也可以叫做buckets(一个可用链表也就是一个bucket)。我们可以使用下面的命令将shared pool的内容转储出来看看这些bucket。 
alter session set events 'immediate trace name heapdump level 2'; 
然后打开产生的转储文件,找到“FREE LISTS”部分,可以发现类似如下图二所示的内容。 

    
      图二 
     这是在9i下产生的bucket列表,9i以前的可用chunk的管理方式是不一样的。我们可以看到,可用的chunk链表(也就是bucket)被分成了254个,每个bucket上挂的chunk的尺寸是不一样的,有一个递增的趋势。我们可以看到,每个bucket都有一个size字段,这个size就说明了该bucket上所能链接的可用chunk的大小尺寸。 

    当一个进程需要shared pool里的一个chunk时,假设当前需要21个单位的空间,则该进程首先到符合所需空间大小的bucket(这里就是bucket 2)上去扫描,以找到一个尺寸最合适的chunk,扫描持续到bucket的最末端,直到找到完全符合尺寸的chunk为止。如果找到的chunk的尺寸比需要的尺寸要大,则该chunk就会被拆分成两个chunk,一个chunk被用来存放数据,而另外一个则成为free类型的chunk,并被挂到当前该bucket上,也就是bucket 2上。然而,如果该bucket上不含有任何需要尺寸的chunk,那么就从下一个非空的bucket上(这里就是bucket 3)获得一个最小的chunk。如果在剩下的所有bucket上都找不到可用的chunk,则需要扫描已经使用的recreatable类型的chunk链表,从该链表上释放一部分的chunk出来,因为只有recreatable类型的chunk才是可以被临时移出内存的。当某个chunk正在被使用时(可能是用户正在使用,也可能是使用了dbms_shared_pool包将对象钉在shared pool里),该chunk是不能被移出内存的。比如某个SQL语句正在执行,那么该SQL语句所对应的游标对象是不能被移出内存的,该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会被锁定很长的时间,这也是8i以前的shared pool latch争用的主要原因。而如果增加shared pool尺寸的话,仅仅是延缓shared pool latch的争用,而到最后,就会因为小的free chunks的数量越来越多,争用也会越来越严重。而到了9i以后,由于大大增加了可用chunk链表(也就是bucket)的数量,同时,每个bucket所管理的可用chunk的尺寸递增的幅度非常小,于是就可以有效的将可用的chunk都均匀的分布在所有的bucket上。这样的结果就是每个bucket上所挂的free类型的chunk都不多,所以在查找可用chunk而持有shared pool latch的时间也可以缩短很多。 

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

2.1 library cache概述 
    library cache最主要的功能就是存放用户提交的SQL语句、SQL语句相关的解析树(解析树也就是对SQL语句中所涉及到的所有对象的展现)、执行计划、用户提交的PL/SQL程序块(包括匿名程序块、存储过程、包、函数等)以及它们转换后能够被oracle执行的代码等。为了对这些内存结构进行管理,还存放了很多控制结构,包括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程序块到oracle的shared pool以后,在library cache中生成的一个可执行的对象,这个对象就叫做游标(cursor)。不要把这里的游标与标准SQL(ANSI SQL)的游标混淆起来了,标准SQL的游标是指返回多条记录的SQL形式,需要定义、打开、关闭。下面所说到的游标如无特别说明,都是指library cache中的可执行的对象。游标是可以被所有进程共享的,也就是说如果100个进程都执行相同的SQL语句,那么这100个进程都可以同时使用该SQL语句所产生的游标,从而节省了内存。每个游标都是由library cache中的两个或多个对象所体现的,至少两个对象。一个对象叫做父游标(parent cursor),包含游标的名称以及其他独立于提交用户的信息。从v$sqlarea视图里看到的都是有关父游标的信息;另外一个或多个对象叫做子游标(child cursors),如果SQL文本相同,但是可能提交SQL语句的用户不同,或者用户提交的SQL语句所涉及到的对象为同名词等,都有可能生成不同的子游标。因为这些SQL语句的文本虽然完全一样,但是上下文环境却不一样,因此这样的SQL语句不是一个可执行的对象,必须细化为多个子游标后才能够执行。子游标含有执行计划或者PL/SQL对象的程序代码块等。 

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

    我们来列举一个最简单的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就可以了。

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

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

    对象句柄存放了对象的名称(name)、对象所属的命名空间(namespace)、有关对象的一些标记(比如对象是否为只读、为本地对象还是远程对象、是否被pin在内存中等等)以及有关对象的一些统计信息等。而且,对象句柄中还存放了当前正在lock住和pin住该对象的用户列表、以及当前正在等待lock和pin该对象的用户列表。对象句柄中存放的最重要的内容就是指向Heap 0对象的指针了。Heap 0用来存放与对象有直接关系的一些信息,比如对象类型、对象相关的表(比如依赖表、子表等)、指向对象的其他数据块的指针(这些数据块指向了实际存放SQL文本、PL/SQL代码、错误信息等的大内存块,这些大内存块依次叫做Heap 1、2、3、4等)等信息。 

    Heap是通过调用服务器进程进行分配的,任何对象都具有heap 0,至于还应该分配哪些其他的heap则是由对象的类型决定的,比如SQL游标具有heap 1和 6,而PL/SQL程序包则具有heap 1、2、3和4。按照heap的使用情况,oracle会在SGA(library cache)、PGA或UGA中分配heap,但是heap 0始终都是在library cache中进行分配的。如果所请求的heap已经在SGA中分配了,则不会在PGA中再次分配heap。Heap是由一个或多个chunk组成的,这些chunk可以是分散的分布在library cache中的,不需要连续分布。

    如上图三中所看到的heap 0实际上是指heap 0的句柄,其中包含的对象包括: 
1) object type:library cache中的对象类型包括:表、视图、索引、同名词等等。每个对象只能有一个object type,根据object type将对象归类到不同的namespace里。一个object type对应一个namespace,但是一个namespace可能对应多个object type。这样的话,查找一个对象时,只要在该对象所属的namespace中去找就可以了。比较常见的namespace包括: 
a) SQL AREA:也可以叫做CRSR,表示shared cursor,存放共享的SQL语句。 
b) TABLE/PROCEDURE:存放的object type包括:table、view、sequence、synonym、 procedure的定义、function的定义以及package的定义。 
c) BODY:存放procedure的实际代码、function的实际代码以及package的实际代码。 
d) TRIGGER:存放的object type为trigger。 
e) INDEX:存放的object type为index。 
2) object name:对象名称由三部分组成: 
a) Schema的名称,对于共享游标(SQL语句或PL/SQL程序块)来说为空。 
b) 对象名称。分为两种情况:对于共享游标(SQL语句或PL/SQL程序块)来说,其对象名称就是SQL的语句本身;而对于其他对象(比如表、视图、索引等)就是其在数据字典中的名称。 
c) Database link的名称。这是可选的,如果是本地对象,则为空。 
这样,对象的名称的格式为:SCHEMA.NAME@DBLINK。比如,可以为hr.employees@apac.com,也可以为hr.employees等。 
3) flags:flags主要用来描述对象是否已经被锁定。对象具有三种类型的flag: 
a) public flag:表示对象上没有锁定(pin)或者latch。 
b) status flag:表示对象上存在锁定(pin),说明对象正在被创建或删除或修改等。 
c) specitial flag:表示对象上存在library cache latch。 
4) tables:对每个对象,都会维护以下一串tables中的若干个: 
a) dependency table:含有当前对象所依赖的其他对象。比如一个视图可能会依赖其组成的多个表、一个存储过程可能依赖其中所调用的其他存储过程、一个游标可能依赖其中所涉及到的多个表等。Dependency table中的每个条目都指向一块物理内存,该物理内存中含有当前对象所依赖的对象的句柄。 
b) child table:含有当前对象的子对象,只有游标具有child table。Child table中的每个条目都指向一个可执行的SQL命令所对应的句柄。 
c) translation table:包含当前对象所引用的名称是如何解释为oracle底层对象的名称,只有游标具有translation table。 
d) authorization table:包含该对象上所对应的权限,一个条目对应一个权限。 
e) access table:对于dependency table中的每一个条目,都会在access table中存在对应的一个或多个条目。比如,假设对象A依赖对象B,那么在A的dependency table和access table中都会存在一个条目指向B。位于access table中的指向B的条目说明了对B具有什么样的访问类型,从而也就说明了用户要执行A则必须具有对B的权限。 
f) read-only dependency table:类似于dependency table,但是存放只读的对象。 
g) schema name table:包含authorization table中的条目所属的schema。 
5) data blocks:对象的其他信息会存放在不同的heap中,为了找到这些heap,会在heap 0中存放多个(最多16个,但是这16个data block不会都用到)data blocks结构,每个data block含有指向这些实际heap内存块的指针。 
除了heap 0以外,还有11个heap,根据对象的不同进行分配,并存放了不同的内容: 
1) Heap 1:存放PL/SQL对象的源代码。 
2) Heap 2:存放PL/SQL对象的解析树,这有个好听的名字: DIANA。 
3) Heap 3:存放PL/SQL对象的伪代码。 
4) Heap 4:存放PL/SQL对象的基于硬件的伪代码。 
5) Heap 5:存放了编译时的错误信息。 
6) Heap 6:存放了共享游标对象的SQL文本。 
7) Heap 7:可用空间。 
8) Heaps 8–11:根据对象的不同而使用的子heap。 
我们可以通过查询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; 
2.2 转储library cache 

0

阅读 (206)   评论   (0) 收藏 (0)   转载 (2)   喜欢   打印 举报
已投稿到:

Shared pool深入分析及性能调整(二)

  (2012-04-06 12:39:40)
标签: 

杂谈

分类: oracle

2.2 转储library cache 
oracle提供了命令可以对library cache中的内容进行转储。于是我们可以对library cache进行转储,从而对上面所说的library cache的内容进行验证。 
ALTER SESSION SET EVENTS 'immediate trace name library_cache level N'; 
这里的N可以取的值分别为: 
1 转储library cache的统计信息 
2 转储hash表的汇总信息 
4 转储library cache object的基本信息 
8 转储library cache object的详细信息 
16 转储heap size的信息 
32 转储heap的详细信息 
在测试之前,我们先创建一个测试表,然后再显示该表的数据。从而在library cache中放入一些数据。 
SQL> create table sharedpool_test as select * from dba_objects where rownum<10; 
SQL> select object_id,object_name from sharedpool_test; 
以level 1转储整个library cache。 
SQL> ALTER SESSION SET EVENTS 'immediate trace name library_cache level 1'; 

    打开跟踪文件可以看到类似这样的信息,这实际就是v$librarycache里记录的信息,只不过v$librarycache中记录的是根据下面的信息合并汇总以后得到的。

namespace gets hit ratio pins hit ratio reloads invalids 
-------------- --------- --------- --------- --------- ---------- ---------- 
CRSR 563 0.815 2717 0.916 15 0 
TABL/PRCD/TYPE 403 0.730 568 0.653 0 0 
BODY/TYBD 2 0.000 2 0.000 0 0 
…………………… 
    然后,我们分别以level 4、8、16、32分别对library cache进行转储,生成的转储文件分别以4#、8#、16#和32#来表示。 
打开4#文件,然后直接查找“select object_id,object_name from sharedpool_test”,因为我们前面说到过,对于SQL语句来说,整个SQL语句的文本就是library cache object的名称。于是,我们可以发现类似下图四所示的内容: 

 

      图四 
    这里的BUCKET 62658就相当于图二中的2号bucket。该bucket上只挂了一个对象,其对象句柄号为6758cdbc。在这个对象句柄里存放了很多信息,这里可以看到该对象的namespace为CRSR,也就是SQL AREA。可以看到该SQL语句的hash值为541cf4c2,将其转换为十进制以后可以直接到v$sql中找到该SQL语句。我们还可以看到很复杂的flags字段,它会包括很多标记,比如RON表示只读(Read Only),SML表示当前句柄尺寸比较小(Small)等。而下面的lwt则表示正在等待lock的对象列表(Lock Waiters),对应图三中的“Lock Waiters”;ltm则表示临时正在持有lock的对象列表(Lock Temporary),对应图三中的“Lock Owners”;pwt则表示正在等待pin的对象列表(Pin Waiters)对应图三中的“Pin Waiters”;ptm则表示临时正在持有pin的对象列表(Pin Temporary),对应图三中的“Pin Owners”。再往下看,可以看到CHILDREN部分,这部分就是前面所说过的子游标的信息了。实际上,指向heap 0的指针也位于这一部分,这个指针也就是6758c840。
 
SQL> select sql_text from v$sql where hash_value=to_number('541cf4c2','xxxxxxxx'); 
SQL_TEXT 
-------------------------------------------------------------------------------- 
select object_id,object_name from sharedpool_test 

     然后,我们打开8#文件,查找6758c840,可以看到如下图五所示的内容。这就是heap 0中所包含的内容。可以看到该heap 0的handle正是6758c840,type为CRSR。还可以看到几个重要的table,这些table都是我们前面介绍过的,包括DEPENDENCIES、ACCESSES、TRANSACTIONS。从前面我们已经知道dependency table记录的是SQL语句所依赖的对象,这里我们可以看到我们的SQL语句依赖一个对象,同时该对象的handle为 675d0d74,很明显,它一定指向sharedpool_test表。同时,我们可以看到transaction table所记录的oracle底层解析的对象的handle也是675d0d74,它与dependency table所记录的对象是一样的,说明这个表是实实在在的表,而不是一个同名词。
 
  


                 图五 
    于是我们继续在8#文件里查找675d0d74,也就是找到library cache中记录SQL所引用的对象的部分。 
我们可以看到类似下图六所示的内容。从name列中可以看到,该对象正是sharedpool_test表,同时该表所在的schema为COST。而且从type为TABL也可以看到,对象sharedpool_test是一个表。 

    

     图六 
    我们再次回到图五,也就是记录heap 0的部分。我们可以看到最后一部分是DATA BLOCKS,从我们前面介绍过的内容可以知道这部分的记录指向了其他的heap内存块。我们从data#列上可以知道,该SQL存在两个相关的heap,编号为0和6。我们知道,heap 0存放了SQL语句本身所涉及到的对象以及若干种表等的信息,而heap 6则存放了SQL语句的文本、执行计划等。于是,我们可以到32#文件中查找6758c7d0(heap 0)和67587c34(heap 6),如下图七所示。我们同时可以看到owner的值,实际上这正是在图五中的object的代号。同时从heap的name处也可以看到,heap 0为library cache,而heap 6为sql area,这也说明了这两个不同的heap所存放的不同内容。 

    


    图七 

2.3 dictionary cache概述 
    dictionary cache专门用来存放SYS schema所拥有的对象的内存区域。使用dictionary cache时以行为单位,而不像其他比如buffer cache以数据块为单位,因此dictionary cache也叫做row cache。构造dictionary cache的目的是为了加快解析SQL语句的速度,因为dictionary cache里存放了所有表的定义、Storage信息、用户权限信息、约束定义、回滚段信息、表的统计信息等。 
    而这些信息都是在解析过程中必须用到的。假设oracle在解析SQL的过程中,发现dictionary cache里没有该SQL所引用的表的定义信息,则oracle必须到磁盘上system表空间里找到这个引用表的定义信息,并将这些定义信息加载到dictionary cache里。这个从磁盘上获取数据字典数据的过程就叫做递归SQL(Recursive SQL)。通常来说,当我们执行一条新的SQL语句时,都会产生很多次的递归调用,也会产生很多的递归SQL。比如我们来下面这个例子。 
   SQL> set autotrace traceonly stat; 
   SQL> select * from sharedpool_test; 
   Statistics 
   ---------------------------------------------------------- 
    185 recursive calls 
    0 db block gets 
    25 consistent gets 
   ………… 

    从这里可以很明显看到执行该SQL产生了185次的递归调用,这185次的递归调用将表sharedpool_test相关的信息,比如列定义、统计信息等,都加载到了dictionary cache里。当我们再次执行该SQL时,会发现recursive calls变成了0,因为dictionary cache里已经包含解析SQL所需要参照的数据字典了。 
   

转储dictionary cache 
  我们可以使用如下命令对dictionary cache进行转储。 
  ALTER SESSION SET EVENTS 'immediate trace name row_cache level N'; 

这里的N可以取的值分别为: 

1 转储dictionary cache的统计信息 ;
2 转储hash表的汇总信息 ;
8 转储dictionary cache中的对象的结构信息;

  如果对level 1进行转储,可以看到转储出来的内容,很明显,就是v$rowcache里的内容。每一种数据字典都有一行记录来表示。比如有tablespace相关的数据字典等。 

  如果以level 2转储的话,可以看到类似如下的内容。这里有23个hash表对dictionary cache中的对象进行管理,每个hash表都对应了一种数据字典,同时有一个名为row cache objects的latch来控制并发访问。可以看到,v$latch_children里名为“row cache objects”的记录数量也是23。 

ROW CACHE HASH TABLE: cid=0 ht=66BD90B0 size=32 

………… 

ROW CACHE HASH TABLE: cid=1 ht=66BD78B0 size=256 

………… 

ROW CACHE HASH TABLE: cid=22 ht=66DA5590 size=512 

………… 

shared pool的内部管理机制 
3.1解析SQL语句的过程 
为了将用户写的可读的SQL文本转化为oracle认识的且可执行的语句,这个过程就叫做解析过程。 
解析分为硬解析和软解析。当一句SQL第一次被执行时必须进行硬解析。 
当客户端发出一条SQL语句(也可以是一个存储过程或者一个匿名PL/SQL块)进入shared pool时 
(注意,我们从前面已经知道,oracle对这些SQL不叫做SQL语句,而是称为游标(cursor)。因为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。然后,进行硬解析过程。硬解析包括以下几个步骤: 
1) 对SQL语句进行语法检查,看是否有语法错误。比如没有写from等。如果有,则退出解析过程。 
2) 到数据字典里校验SQL语句涉及的对象和列是否都存在。如果不存在,则退出解析过程。 
3) 将对象进行名称转换。比如将同名词翻译成实际的对象等。如果转换失败,则退出解析过程。 
4) 检查游标里用户是否具有访问SQL语句里所引用的对象的权限。如果没有权限,则退出解析过程。 
5) 通过优化器创建一个最优的执行计划。这一步是最消耗CPU资源的。 
6) 将该游标所产生的执行计划、SQL文本等装载进library cache的若干个heap中。 
在硬解析的过程中,进程会一直持有library cach latch,直到硬解析结束。硬解析结束以后,会为该SQL产生两个游标,一个是父游标,另一个是子游标。父游标里主要包含两种信息:SQL文本以及优化目标(optimizer goal)。父游标在第一次打开时被锁定,直到其他所有的session都关闭该游标后才被解锁。当父游标被锁定的时候是不能被交换出library cache的,只有在解锁以后才能被交换出library cache,这时该父游标对应的所有子游标也被交换出library cache。子游标包括游标所有的信息,比如具体的执行计划、绑定变量等。前面图四中看到的CHILDREN部分就是子游标所对应的handle的信息。子游标随时可以被交换出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语句完全相同,但是所引用的对象属于不同的schema时,都会创建一个新的子游标。 
如果在bucket中找到了该SQL语句,则说明该SQL语句以前运行过,于是进行软解析。软解析是相对于硬解析而言的,如果解析过程中,可以从硬解析的步骤中去掉一个或多个的话,这样的解析就是软解析。软解析分为以下三种类型。 
1) 第一种是某个session发出的SQL语句与library cache里其他session发出的SQL语句一致。这时,该解析过程中可以去掉硬解析中的5和6这两步,但是仍然要进行硬解析过程中的2、3、4步骤:也就是表名和列名检查、名称转换和权限检查。 
2) 第二种是某个session发出的SQL语句与library cache里该同一个session之前发出的SQL语句一致。这时,该解析过程中可以去掉硬解析中的2、3、5和6这四步,但是仍然要进行权限检查,因为可能通过grant改变了该session用户的权限。 
3) 第三种是当设置了初始化参数session_cached_cursors时,当某个session对相同的cursor进行第三次访问时,将在该session的PGA里创建一个标记,并且该游标即使已经被关闭也不会从library cache中交换出去。这样,该session以后再执行相同的SQL语句时,将跳过硬解析的所有步骤。这种情况下,是最高效的解析方式,但是会消耗很大的内存。 
我们先来举一个例子说明如果在解析过程中发生语法或语义错误时,在shared pool中是怎样体现的。 
SQL> select object_type fromm sharedpool_test111; 
ORA-00942: 表或视图不存在 
然后我们以level 16转储library cache,并打开转储文件,找到相应的部分,如下图八所示。可以看到, 
该SQL语句在语法上是错误的(from写成了fromm),oracle仍然在shared pool中为其分配了一个chunk,然后该chunk进入library cache,并在library cache中分配了一个bucket,同时也生成了heap 0,但是该heap 0中不存在相应的一些如dependency table等table的部分,以及data block的部分。我看到有些资料上说SQL语句是先进行语法分析,如果通过语法分析以后,则应用hash函数生成hash值,然后再去shared pool中分配chunk。实际上从这个实例已经可以看出,这个说法是错误的。oracle始终都是先对SQL生成hash值(不论该SQL语法上是否正确),再根据hash值到对应的可用chunk链表(也就是bucket)里分配chunk,然后进入语法解析等解析过程。 

 


图八 
我们再举一个例子来说明解析正确的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 
然后,我们以level 16来转储library cache。可以看到如下图九所示的内容。很明显的看到,子游标的
部分包含两条记录,这也就说明该SQL语句产生了两个子游标。虽然我们从SQL文本上看,前后两次执行的SQL语句是一样的。只有绑定变量的类型发生了改变,第一次是number型,而第二次是varchar2型。可正是这数据类型的变化导致了该SQL语句的执行计划不能得到共享,从而产生了两个子游标。这时,我们根据子游标的两个handle:6757f358和674440fc找到对应的heap 0的话,就可以看到这两个heap 0中所记录的heap 6是两个完全不同的内存块,这也说明前后两次执行SQL并没有真正得到共享。 

 

图九 

我们还可以根据该SQL的hash值(f390fb6f)来看看动态性能视图里是如何表现的。 
SQL> select to_number('f390fb6f','xxxxxxxx') from dual; 
TO_NUMBER('F390FB6F','XXXXXXXX 
------------------------------ 
4086365039 
SQL> select sql_text,version_count from v$sqlarea where hash_value=4086365039; 
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 hash_value=4086365039; 
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参数等)等。 

3.2 library cache的并发控制  
  由于library cache是可以被所有进程同时访问并修改的,因此就必然存在一个并发控制的问题。比如对于前面我们举的如图九所示的例子来说,第一次使用number类型的绑定变量执行的SQL语句产生的游标挂在bucket 64367上。而当我们第二次使用varchar2类型的绑定变量再次执行该SQL语句时,oracle需要访问该bucket 64367上的句柄,发现不能共用执行计划时,还要修改该句柄,向CHILDREN部分添加一条指向另外一个子游标的句柄。在很多进程并发执行的情况下,那么当修改挂在bucket 64367上的句柄的时候,必须防止其他进程同时修改所访问的句柄。因为如果不防止这种情况的出现,那么假如这时正好也有一个进程也是使用varchar2类型的绑定变量执行该相同的SQL时,那么也会向CHILDREN部分添加一条子游标的记录,那么CHILDREN部分就会出现两条varchar2的执行计划,而实际上这两个执行计划是一样的,是完全可以合二为一的,这样也就达不到SQL共享的目的。同时还要考虑,当oracle在向某个heap(比如heap 0和heap 6)填入SQL文本、执行计划等数据的过程中,要防止该heap又被其他进程分配掉的情况出现。如果不防止的话,那这个heap的数据就被两个进程同时写,那里面的数据一定是混乱而无法使用的了。

    为了有效的解决上面所说的并发性的问题,oracle使用三种结构来完成对library cache的并发控制:lock、pin和library cache latch。简单来说,进程如果要访问或者修改library cache里的对象,首先必须获得library cache latch,然后获得handle上的lock,最后获得heap上的pin,访问或修改结束以后,释放pin、lock和latch。

  lock是落在library cache里的对象句柄上的,用来管理并发性。按照前面所说的例子,当多个进程同时修改bucket 64367上的句柄的时候,只有一个进程能够获得该句柄上的lock,其他进程必须等待(体现的等待事件就是library cache lock)。同时,尝试获得某个句柄上的lock也是将游标句柄对象加载到shared pool里的唯一方式。也就是说,当客户端发出某个SQL语句时,oracle对该SQL语句运用hash函数生成hash值,然后到该hash值所对应的library cache的bucket里试图找到对应的句柄并lock该句柄时,如果发现该句柄不存在(可能是由于该SQL语句是一条全新的SQL语句,或者以前该SQL语句执行过但是现在被交换出了library cache),则会将该SQL游标所对应的句柄加载到library cache里。 

    目前有三种lock模式,分别是:share、exclusive和null。如果某个进程只是要读取句柄里的信息时,会对该句柄添加share模式的lock,比如当编译某个存储过程时,进程会去读取该存储过程所引用的子存储过程等,这时其他进程可以对该相同的句柄添加share和null模式的lock;如果某个进程需要修改对象里的信息时,就会对该句柄添加exclusive模式的lock,比如删除某个存储过程就会添加exclusive模式的lock,这时其他进程只能对该相同的句柄添加null模式的lock;null模式的lock比较特殊,在任何可以执行的对象上(比如存储过程、视图、函数等等)都存在该null模式的lock。你可以随意打破该模式的lock,这时该lock所在的对象就失效了,需要重新编译。当SQL开始解析时,或获得null模式的lock,然后会一直加在该对象上,直到某些会引起对象失效的DDL发生在对象或对象所依赖的其他对象上,这时该lock被打破。当发生null模式的lock时,其他进程可以对该相同的句柄添加任何模式的lock。 

    而pin则是落在heap上的,用来防止多个进程同时更新同一个heap。pin的优先级比lock要低,获得pin之前必须先获得lock。同样按照前面所说的例子,当第二次使用varchar2类型的绑定变量执行相同的SQL语句时,该进程首先会获得bucket 64367的句柄上的lock,根据该句柄里所记录的heap发现不能共用时,到shared pool中分配可用的chunk作为heap(包括heap 0和heap 6等)的空间,并获得该heap上的pin,然后在句柄里添加一条子游标记录,以指向所分配的heap 0的句柄。当pin住了heap以后,进程就向heap中写入数据,结束以后释放pin,最后释放lock。当某个进程获得了句柄上的lock,但是不能pin住该句柄所对应的heap时,该进程就必须等待(体现的等待事件就是library cach pin)。与lock相同,当进程试图pin住某个heap但是发现该heap不存在时,就会同时将该heap加载到library cache里同时pin住它。

    pin有两种模式:share和exclusive。当某个进程只需要读取heap中的信息时,会对该heap执行share模式的pin。如果进程需要修改heap时,则会先对该heap执行share模式的pin以便对heap进行错误和安全检查,通过以后,再对该heap执行exclusive模式的pin,从而对该heap进行修改。 

    从上面对lock和pin的描述中可以看出,lock本身不是一个原子的操作,也就是说要完成lock需要执行一系列的操作步骤(包括pin住heap等)。因此为了防止lock的过程被其他进程打破,oracle使用library cache latch来管理lock。也就是说,如果某个进程在进行lock之前,必须先获得library cache latch,如果不能获得该latch,就必须等待。当lock过程结束以后,释放该latch。 
oracle提供了多个library cache latch(这样,每个library cache latch都称为子latch)来保护library cache中的bucket。这些子latch的数量由一个隐藏参数决定:_kgl_latch_count。该参数缺省值为大于等于系统中CPU个数的最小的素数。比如在一个具有4个CPU的生产环境中,library cache latch的个数为5,如下所示。但是oracle内部(9i版本)规定了library cache latch的最大个数为67,即便将这个隐藏参数设置为100,library cache latch的数量也还是67个。 
SQL> select x.ksppinm, y.ksppstvl, x.ksppdesc 
2 from x$ksppi x , x$ksppcv y 
3 where x.indx = y.indx 
4 and x.ksppinm like '\_%' escape '\' 
5 and ksppinm like '%kgl_latch_count%' 
6 ; 
KSPPINM KSPPSTVL KSPPDESC 
-------------------- ---------- ---------------------------------------- 
_kgl_latch_count 5 number of library cache latches 

具体到每个bucket应该由哪个子latch来管理,则是通过下面这个函数来确定的。 
latch号=mod(bucket号,latch的数量) 
假如还是按照上面的例子,对于bucket 64367来说,假设当前系统具有37个library cache latch,那么会使用24(mod(64367,37)=24)号latch来保护挂在该bucket上的句柄。正是由于这样的算法,可能会导致所有的子latch不能在library cache里的整个bucket链条上均匀分布,有可能出现某个或某几个子latch非常繁忙,而有些子latch则非常空闲。至于如何判断以及解决,可以见下面shared pool的优化部分。 
我们来做两个测试,分别来模拟一下lock和pin。来看看lock和pin是如何控制library cache里的对象的。试验的思路很简单,第一,打开一个session(sess #1)创建一个存储过程,该过程只做一件事情,就是通过调用dbms_lock.sleep进行等待。并在sess #1中调用该存储过程;第二,打开第二个session(sess #2),重新编译该存储过程;第三,打开第三个session(sess #3),删除该存储过程;第四,打开第四个session(sess #4)进行监控。根据前面对lock和pin的描述,我们可以预见,sess #2将等待library cache pin。而sess #3会等待library cache lock。 
试验过程如下,在试验的过程中,不断以level 16转储library cache以更深入的观察lock和pin的变化,以下按照时间顺序排列: 
sess #1 
SQL> create or replace procedure lock_test 
2 is 
3 begin 
4 sys.dbms_lock.sleep(5000); 
5 end; 
6 / 
SQL> exec lock_test; 
sess #4,转储出来的文件编号为F1。 
SQL> ALTER SESSION SET EVENTS 'immediate trace name library_cache level 16'; 
sess #2 
SQL> select sid from v$mystat where rownum=1; 
SID 
---------- 

SQL> alter procedure lock_test compile; --这时该命令停住了。 
sess #4,转储出来的文件编号为F2。 
SQL> ALTER SESSION SET EVENTS 'immediate trace name library_cache level 16'; 
sess #3 
SQL> select sid from v$mystat where rownum=1; 
SID 
---------- 
10 
SQL> drop procedure lock_test; --这时该命令也停住了 
sess #4,转储出来的文件编号为F3。 
SQL> ALTER SESSION SET EVENTS 'immediate trace name library_cache level 16'; 
SQL> select sid,event from v$session_wait where sid in(9,10); 
SID EVENT 
---------- ---------------------------------------------------------------- 
9 library cache pin 
10 library cache lock 
从监控的结果看到,正如我们所预料的,编译存储过程的sess #2(sid为9)正在等待library cache pin,而删除存储过程的sess #3(sid为10)正在等待library cache lock。在转储出来的文件中,我们主要关存储过程lock_test本身在library cache中的变化。在F1中,我们可以看到如下图十的内容。注意其中的lock为N,pin为S。由于sess #1正在执行lock_test存储过程,需要读取该handle所对应的heap里的内容,因此以null模式lock住句柄,同时以share模式pin住了heap。这时该对象句柄可以被其他进程以任何模式锁定,但是该句柄对应的heap只能被其他进程以share模式pin,而不能以exclusive模式pin。 

 


图十 
我们打开发出编译命令以后生成的F2,找到与图十同样的部分,如下图十一所示。由于sess #2会对存储过程lock_test进行编译,因此需要重新刷新该对象的heap中的信息。所以需要以exclusive模式lock住该对象的句柄,同时以exclusive模式pin住该对象的heap。这时,由于当前句柄上存在null模式的lock,因此sess #2申请exclusive的lock能够成功,但是由于当前该句柄对应的heap上已经存在share的pin,因此申请exclusive的pin时,必须等待,这时体现为sess #2等待library cache pin。 

 

     图十一 

    这时,我们发出删除命令以后,很明显的,要删除存储过程lock_test,sess #3必须以exclusive模式获得lock_test句柄的lock。而这时该句柄上已经存在了exclusive模式的lock,于是这时sess #3只有等待sess #2所添加的exclusive模式的lock释放以后才能继续进行。体现在v$session_wait等相关视图里就是等待library cache lock。这时,我们甚至可以发现,整个“drop procedure lock_test”命令都没有出现在library cache里。也就是说,oracle已经为该SQL语句分配了chunk,但是由于无法获得所引用对象的lock,从而使得所分配的chunk还没有能够挂到bucket上去,也就还没有进入library cache里。 
至于如何诊断以及解决这两个等待事件的话,可以见下面shared pool的优化部分。 
   (待续......)

0

阅读 (150)   评论   (0) 收藏 (0)   转载 (0)   喜欢   打印 举报
已投稿到:

Shared pool深入分析及性能调整(三)

  (2012-04-06 13:44:07)
标签: 

杂谈

分类: oracle
4. shared pool的优化 
4.1 共享SQL语句 
       根据上面对shared pool的内部原理的说明,我们已经很清楚的知道,oracle引入shared pool就是为了能够缓存经常使用的SQL语句,从而能够将SQL语句的执行计划缓存在library cache中,这样当第二次执行相同的SQL语句时,就可以跳过硬解析而进行软解析,从而节省了大量的CPU资源。当一句新的SQL语句进入shared pool时,需要分配chunk,这时会持有shared pool latch,直到获得chunk,这是一个潜在的争用点;获得chunk以后,进入library cache时,需要获得library cache latch来保护对lock的获得,这又是一个潜在的争用点。然后,oracle要lock住句柄,才能往里填写内容,这也是一个潜在的争用点;生成执行计划等内容以后,oracle还要pin住若干个heap,才能往里写入实际的数据,这还是一个潜在的争用点。可见,一句新的SQL语句从进入shared pool开始到解析结束,存在一系列的争用点。特别是,当并发用户很多的时候,每个用户都发出对于shared pool来说是新的SQL语句,这时,你会看到CPU非常繁忙,甚至一直处于100%的使用状态,同时这些潜在的争用点都将变成实际的争用点,表现出来就是等待事件非常多,用户响应缓慢甚至没有响应。

       为了尽可能减少新的SQL语句,尽可能多的缓存SQL语句,就必须使得应用程序的SQL语句尽量保持一致,包括各个单词之间的空格一致以及大小写一致等。其中最重要的一点就是要使用绑定变量。对于一个系统来说,SQL语句本身所引用的表和列都是有限的,只有SQL语句中所引用的数据才是无限的,因此将SQL语句中所涉及到的数据都用绑定变量来替代,这样就能使得对于不同的数据,SQL语句看起来都是一样的。

       判断当前系统是否使用了绑定变量,可以使用如下语句获得当前系统的硬解析次数与解析总次数的比例。硬解析次数越少越好,这个比例也越接近于0越好。     
 
    
SQL >   select   t.value   as   total,h.value   as   hard,   2 round (h.value / t.value, 2 )   as   ratio_hardtototal   3   from   v$sysstat t, v$sysstat h   4   where   t.name = ' parse count (total) ' 5   and   h.name = ' parse count (hard) ' 6   / TOTAL HARD RATIO_HARDTOTOTAL   -- -------- ---------- -----------------   2377895510   47207356   0.02
 
   

     

         如果发现硬解析比较高,则可以使用下面的方法找到shared pool里那些没有使用绑定变量的SQL语句,从而提交给开发人员进行修改。         
 
    
break   on   plan_hash_value   on   execnt   on   hash_value skip   1 select d.plan_hash_value plan_hash_value , d.execnt execnt , a.hash_value hash_value , a.sql_text sql_text   from   v$sqltext a, ( select plan_hash_value,hash_value,execnt   from (   select c.plan_hash_value,b.hash_value,c.execnt, rank()   over (partition   by c.plan_hash_value   order   by   b.hash_value)   as   hashrank   from v$sql b, ( select   count ( * )   as   execnt,plan_hash_value   from   v$sql   where plan_hash_value   <>   0 group   by   plan_hash_value   having   count ( * )   >   10 order by   count ( * )   desc   ) c   where   b.plan_hash_value   =   c.plan_hash_value   group by   c.plan_hash_value,b.hash_value,c.execnt )   where   hashrank <= 3   ) d where   a.hash_value   =   d.hash_value   order   by   d.execnt desc ,a.hash_value,a.piece   /
 
   
   

       如果发现系统中大量的没有使用绑定变量,而且系统是由其他第三方供应商提供的,不能做大量的修改从而使用绑定变量。实际上,这样的系统基本就是一个失败的系统,但是如果必须继续使用而又希望能够尽量减少对CPU资源的争用,oracle还提供了一个参数:cursor_sharing。该参数缺省是exact,表示不对传入shared pool中的SQL语句改写。如果设置为similar或force,则oracle会对SQL语句进行改写,将SQL语句中值的部分都用系统生成的变量来替代,从而达到与绑定变量相同的目的。similar表示,当SQL语句中的数值所在的列存在直方图(histogram)信息时,oracle不对SQL语句进行改写,就像设置为exact一样,每次对于不同的值都要进行硬解析;而当表没有经过分析,不存在直方图时,oracle会对SQL语句进行改写,就像设置为force一样,这样每次对于不同的值都会进行软解析。

       但是使用这种方法在不同的oracle版本中可能存在bug,需要在测试环境中仔细测试。同时,将cursor_sharing设置为similar或force以后,会在生成执行计划上产生一些副作用,比如选择错误的索引,以及忽略带有数值的函数索引(比如函数索引为substr(colname,1,6)的情况,因为其中的1和6被系统变量替代了)等。 

       对于某些非常频繁使用的对象,我们还可以使用存储过程:DBMS_SHARED_POOL.KEEP,从而将它们固定在shared pool里,这样被钉住的对象就不会被交换出shared pool,即便刷新shared pool也不能将这些对象刷新出去。如果没有发现这个存储过程,则可以使用oracle脚本:dbmspool.sql 来创建,该脚本位于目录:$ORACLE_HOME/rdbms/admin下。 
该存储过程有两个参数,第一个参数表示要钉在内存里的对象的名字,第二个参数表示要钉住的对象的类型,缺省为P,表示存储过程、包、函数,如果要钉住某个SQL语句,则需要设置该参数为C。对于存储过程、包、函数、触发器以及序列(sequence)等对象,在调用该存储过程时,使用名字对其进行引用。比如:DBMS_SHARED_POOL.KEEP('SALES.PKG_SALES'),这样就将位于SALES下的包PKG_SALES给钉在内存里了。;而对于某条单独的SQL语句来说,则需要使用地址和hash值对其进行引用,地址和hash值可以从v$sqlarea里的address和hash_value列获得,比如对于我们前面测试的SQL语句来说,address就是图二中的handle值,也就是'6758CDBC,hash_value就是图二中的541cf4c2,转换为十进制就是1411183810,则我们将其钉在内存里:DBMS_SHARED_POOL.KEEP('6758CDBC,1411183810',’C’)。如果我们要取消钉住的话,则调用DBMS_SHARED_POOL.UNKEEP即可,该过程的参数与KEEP相同。

       如下例所示,我们将该SQL语句钉在shared pool以后,刷新shared pool也没能将它刷新出去。 
 
    
SQL >   select   address,hash_value   from   v$sqlarea   2   where   sql_text   = ' select object_id,object_name from sharedpool_test   ' ; ADDRESS HASH_VALUE   -- ------ ----------   6758CDBC   1411183810 SQL >   exec DBMS_SHARED_POOL.KEEP( ' 6758CDBC,1411183810 ' , ' C ' ); SQL >   alter   system flush shared pool; SQL >   select   address,hash_value   from   v$sqlarea   2 where   sql_text   =   ' select object_id,object_name from sharedpool_test   ' ; ADDRESS HASH_VALUE   -- ------ ----------   6758CDBC   1411183810 SQL >   exec DBMS_SHARED_POOL.UNKEEP( ' 6758CDBC,1411183810 ' , ' C ' ); SQL >   alter   system flush shared pool; SQL >   select   address,hash_value   from   v$sqlarea   2 where   sql_text   =   ' select object_id,object_name from sharedpool_test   ' ; ADDRESS HASH_VALUE   -- ------ ----------
 
   

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

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

       只要将初始化参数:statistics_level设置为typical(缺省值)或all,就能启动对shared pool的建议功能,如果设置为basic,则关闭建议功能。使用如下的SQL语句显示oracle所建议的shared pool的大小。
 
     
SQL >   SELECT   shared_pool_size_for_estimate, estd_lc_size, estd_lc_memory_objects,   2   estd_lc_time_saved, estd_lc_time_saved_factor,   3   estd_lc_memory_object_hits   4   FROM v$shared_pool_advice; SHARED_POOL_ ESTD_LC ESTD_LC_MEMORY ESTD_LC_TIME ESTD_LC_TIME ESTD_LC_MEMORY SIZE_FOR_ESTIMATE _SIZE _OBJECTS _SAVED _SAVED_FACTOR _OBJECT_HITS   -- --------------- ------- --------------- ------------ ------------- ------------   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为224M时,达到最佳大小。对于设置比224M更大的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
 
    


       

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

       如果设置了Shared Server连接模式,则注意要通过配置large pool(通过设置large_pool参数)。如果不设置large pool,session的PGA会有一部分在shared pool里进行分配,从而加重shared pool的负担。 
4.3 shared pool的统计信息  
    有关shared pool的最重要的统计信息就是parse count (total)和parse count (hard),parse count (total)表示解析的总次数,而parse count (hard)表示硬解析的次数,这个值应该越小越好。

 
    
SQL >   select   *   from   v$sysstat   where   name   in   ( ' parse count (total) ' , ' parse count (hard) ' ); STATISTIC# NAME CLASS VALUE   -- -------- ------------------ -------- ----------   235   parse   count   (total)   64 4989 236   parse   count   (hard)   64   993 对于library cache来说,可以使用如下的统计信息来判断,如果reload - to - pins大于0. 01 ,则说明shared pool设置过小,需要增加shared pool。 SQL >   select   sum (pins) "Executions", sum (reloads) "Cache Misses",   2   sum (reloads) / sum (pins) " reload - to - pins" from   v$librarycache; Executions Cache Misses reload - to - pins   -- -------- ------------ ---------------   4458375   4159   0.0009328510948
 
   

    

    对于dictionary cache来说,可以使用如下的统计信息来判断,如果miss-ratio大于0.02,则说明shared pool设置过小,需要增加shared pool。    
 
    
SQL >   select   sum (gets)   as   gets, sum (getmisses)   as   misses,   2 sum (getmisses) / sum (gets)   as   miss_ratio   from   v$rowcache; GETS MISSES MISS_RATIO   -- -------- ---------- ----------   4762783   15397   0.00323277
 
   

4.4 shared pool的等待事件 
4.4.1 library cache latch 
       对于library cache latch来说,我们可以看看v$latch中按照sleeps排名前5位中是否有它。 
 
      
SQL >   select   latch#,name,sleeps   from   v$latch   where   sleeps != 0   order   by sleeps   desc ; LATCH# NAME SLEEPS   -- -------- -------------------- ----------   4   session allocation   38389 157   library cache   1221 3   process allocation   974 115   redo allocation   841 156   shared pool   755
 
     
       

       我们看到,library cache latch的sleep排名比较靠前。这时,我们在前面已经说过,子library cache latch可能不会在library cache里的整个bucket链条上均匀分布,因此当我们通过查看v$latch发现library cache latch的等待非常高时,先别急着增加latch的个数,或者调整SQL等,而是先去看看v$latch_children中每个子library cache latch的等待是否都比较接近,如果发现这些latch的等待相差很大的话,则说明可能是没有最有效的使用latch。同时注意观察下面的MISSES/GETS列,如果它高于0.01的话,应该调整。

 
      
SQL >   select   latch#,child#,gets, round (ratio_to_report(gets)   OVER   (), 2 ) as   gets_radio,   2   misses, round (ratio_to_report(misses)   OVER   (), 2 )   as misses_radio,misses / gets   3   from   v$latch_children   where   name = ' library cache ' ; LATCH# CHILD# GETS GETS_RADIO MISSES MISSES_RADIO MISSES / GETS   -- -------- ---------- ---------- ---------- ---------- ------------ -----------   157   5   7187577   0.3   160964   0.85   0.022394751 157   4   4308236 0.18   19943   0.11   0.004629040 157   3   4138652   0.17   2453   0.01 0.000592705 157   2   2458795   0.1   448   0   0.000182203 157   1   5974764 0.25   4619   0.02   0.000773084
 
     


       

       比如,从上面我们可以看到,CHILD#为5的子latch的miss占了整个latch的87%,因此,有可能是该子latch管理了过多的library cache object。我们可以使用下面的SQL语句来看看这个子latch到底管了哪些SQL对象: 
       select * from v$db_object_cache where child_latch=5 and namespace='CURSOR'; 
       
       然后,看看该子latch所管理的SQL语句是不是可以改写一下,从而让它关联到其他4个子latch上去。有时候为列添加一个别名或者在where条件子句中添加一个条件“1=1”,就能够将该SQL转换到另外一个子library cache latch来管理,从而通过分散子library cache latch来达到降低该latch等待的目的。

       如果所有的子library cache latch都均匀分布的话,则需要按照前面所说的方法检查SQL语句是否使用了绑定变量、是否大小写一致、空格是否相同等。如果这些都没问题,则检查shared pool是否设置过大,如果设置也合理的话,那就需要按照前面所说的,增加隐藏参数:_kgl_latch_count的值,从而增加library cache latch的数量。 

4.4.2 library cache lock和library cache pin 
       在前面的部分我们已经看到了library cache lock和library cache pin的成因,并且也模拟出了这两个等待事件。那么当我们发现在v$session_wait里出现了这两个等待事件中的某一个式时,该怎么办呢? 
还是按照上面我们模拟这两个等待事件的例子。这时我们查看v$session_wait,发现出现这两个等待。
 
 
        
SQL >   select   sid,event   from   v$session_wait   where   event   like   ' library% ' ; SID EVENT   -- -------- ----------------------------------------------------------------   9   library cache pin   10   library cache lock
 
       

    这时,我们可以使用如下的SQL语句来获得是哪个session通过哪条SQL语句获得了library cache pin,而哪个session又试图通过哪条SQL语句去申请library cache pin。     

 
        
SQL >   SELECT   distinct 2   decode(kglpnreq, 0 , ' holding_session:   '   || s.sid, ' waiting_session:   '   ||   s.sid) sid,   3   s.SERIAL#,   4   kglpnmod "Pin - Mode",   5   kglpnreq "Req - Pin",   6   a.sql_text,   7   kglnaown "Owner",   8 kglnaobj "Object"   9   FROM   x$kglpn p, v$session s, v$sqlarea a, v$session_wait sw, x$kglob x   10   WHERE   p.kglpnuse   =   s.saddr   11   AND kglpnhdl   =   sw.p1raw   12   and   kglhdadr   =   sw.p1raw   13   and   event   =   ' library cache pin ' 14   and   (a.hash_value, a.address)   IN 15   ( select DECODE(sql_hash_value,   0 , prev_hash_value, sql_hash_value),   16 DECODE(sql_hash_value,   0 , prev_sql_addr, sql_address)   17   from   v$session s2   18   where   s2.sid   =   s.sid); SID SERIAL# Pin - Mode Req - Pin SQL_TEXT Owner Object   -- ------------- ------- ------ ------- ------------------------------ ------ ----------   holding_session: 8   22   2   0   BEGIN lock_test;   END ; COST LOCK_TEST waiting_session: 9   16   0   3   alter procedure   lock_test compile COST LOCK_TEST
 
       



同样,我们可以使用下面的SQL语句来显示哪个session正持有lock或者pin,而哪个session又在等待lock或者pin。
注意,我们不能获得正在等待lock的session所发出的SQL语句,因为该session都还没
有获得library cache对象句柄的控制权,就更谈不上将SQL语句写入heap里了,所以我们无法获得该session所发出的SQL语句。
因此,我们只能显示哪个session正在等待lock。 
 
        
SQL >   select   2   w1.sid waiting_session,   3   h1.sid holding_session,
4   w.kgllktype lock_or_pin,   5   w.kgllkhdl address,
6 decode(h.kgllkmod, 0 , ' None ' , 1 , ' Null ' , 2 , ' Share ' , 3 , ' Exclusive ' , ' Unknown ' )
mode_held,
  7 decode(w.kgllkreq, 0 , ' None ' , 1 , ' Null ' , 2 , ' Share ' , 3 , ' Exclusive ' , ' Unknown ' )
mode_requested
  8   from   dba_kgllock w, dba_kgllock h, v$session w1, v$session h1   9  
where   (((h.kgllkmod   !=   0 )   and   (h.kgllkmod   !=   1 )   10   and   ((h.kgllkreq   = 0 )   or   (h.kgllkreq   =   1 )))   11  
and   (((w.kgllkmod   =   0 )   or   (w.kgllkmod   =   1 ))   12   and   ((w.kgllkreq   !=   0 ) and   (w.kgllkreq   !=   1 ))))   13  
and   w.kgllktype   =   h.kgllktype   14   and   w.kgllkhdl   =   h.kgllkhdl   15   and w.kgllkuse   =   w1.saddr   16  
and   h.kgllkuse   =   h1.saddr; WAITING_SESSION HOLDING_SESSION LOCK_OR_PIN ADDRESS MODE_HELD MODE_REQUESTED
-- ------------- --------------- ----------- -------- --------- --------------
9 8 Pin 675C3078 Share Exclusive 10 9 Lock 675C3078 Exclusive Exclusive
 
       
 
      
   从上面的结果可以很清楚的看到,sid为8的session阻止了sid为9的session获得pin,而sid为9的session又阻止了sid为10的session获得lock。

       通常,这两个等待都是由于DDL所引起的,因此我们还可以快速的通过查看dba_ddl_lock视图来看当前哪些session正在对表lock_test进行操作。我们可以看到下面的结果中,sid为9的session正以Exclusive模式获得表lock_test,而sid为10的session则正以Exclusive模式申请对该表的lock。 
SQL> select session_id,owner,type,mode_held,mode_requested 
2 from dba_ddl_locks where owner='COST' and name='LOCK_TEST'; 
SESSION_ID OWNER TYPE MODE_HELD MODE_REQUESTED 
---------- ------------ -------------------- --------- -------------- 
8 COST Table/Procedure/Type Null None 
9 COST Table/Procedure/Type Exclusive None 
10 COST Table/Procedure/Type None Exclusive 


0

阅读 (199)   评论   (0) 收藏 (0)   转载 (0)   喜欢   打印 举报
已投稿到:
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
深入解析OracleDBA入门进阶与诊断案例 扫描版 作  者:盖国强 著 出 版 社:人民邮电出版社 出版时间:2009-1-1 页  数:527 内容简介   针对数据库的启动和关闭、控制文件与数据库初始化、参数及参数文件、数据字典、内存管理、Buffer Cache与Shared Pool原理、重做、回滚与撤销、等待事件、性能诊断与SQL优化等几大Oracle热点主题,本书从基础知识入手,深入研究相关技术,并结合性能调整及丰富的诊断案例,力图将Oracle知识全面、系统、深入地展现给读者。   本书给出了大量取自实际工作现场的实例,在分析实例的过程中,兼顾深度与广度,不仅对实际问题的现象、产生原因和相关的原理进行了深入浅出的讲解,更主要的是,结合实际应用环境,提供了一系列解决问题的思路和方法,包括详细的操作步骤,具有很强的实战性和可操作性,适用于具备一定数据库基础、打算深入学习Oracle技术的数据库从业人员,尤其适用于入门、进阶以及希望深入研究Oracle技术的数据库管理人员。 目录 第1章 数据库的启动和关闭   1.1 数据库的启动   1.2 数据库的访问   1.3 数据库的关闭  第2章 控制文件与数据库初始化   2.1 控制文件的内容   2.2 SCN   2.3 检查点(Checkpoint)   2.4 数据库的初始化  第3章 参数及参数文件   3.1 初始化参数的分类   3.2 参数文件   3.3 诊断案例之一:参数文件   3.4 诊断案例之二:RAC环境参数文件  第4章 数据字典   4.1 数据字典概述   4.2 内部RDBMS(X$)表   4.3 数据字典表   4.4 静态数据字典视图   4.5 动态性能视图   4.6 最后的验证  第5章 内存管理   5.1 PGA管理   5.2 SGA管理   5.3 Oracle的内存分配和使用  第6章 Buffer Cache与Shared Pool原理   6.1 Buffer Cache原理   6.2 Shared Pool的基本原理  第7章 重做(Redo)   7.1 Redo的作用   7.2 Redo的原理   7.3 Redo与Latch   7.4 Oracle 9i Redo的增强   7.5 Oracle 10g Redo的增强   7.6 Redo的内容   7.7 产生多少Redo   7.8 Redo写的触发条件   7.9 Redo Log Buffer的大小设置   7.10 commit做了什么?   7.11 日志的状态   7.12 日志的块大小   7.13 日志文件的大小   7.14 如何调整日志文件大小   7.15 为什么热备份期间产生的Redo要比正常的多   7.16 能否不生成Redo   7.17 Redo故障的恢复   7.18 诊断案例一:通过Clear日志恢复数据库   7.19 诊断案例二:日志组过度激活的诊断   附录 数值在Oracle的内部存储  第8章 回滚与撤销   8.1 什么是回滚和撤销   8.2 回滚段存储的内容   8.3 并发控制和一致性读   8.4 回滚段的前世今生   8.5 Oracle 10g的UNDO_RETENTION管理增强   8.6 UNDO_RETENTION的内部实现   8.7 Oracle 10g In Memory Undo新特性   8.8 Oracle 11g UNDO表空间备份增强   8.9 回滚机制的深入研究   8.10 Oracle 9i闪回查询的新特性   8.11 使用ERRORSTACK进行错误跟踪   8.12 Oracle 10g闪回查询特性的增强   8.13 ORA-01555成因与解决   8.14 Oracle 11g闪回数据归档   8.15 AUM下如何重建UNDO表空间   8.16 使用Flashback Query恢复误删除数据   8.17 诊断案例之一:释放过度扩展的UNDO空间   8.18 特殊情况的恢复   8.19 诊断案例之二:回滚段损坏的恢复  第9章 等待事件   9.1 等待事件的源起   9.2 从等待发现瓶颈   9.3 Oracle 10g的增强   9.4 顶级等待事件   9.5 重要等待事件  第10章 性能诊断与SQL优化   10.1 使用AUTOTRACE功能辅助SQL优化   10.2 获取SQL执行计划的方法   10.3 捕获问题SQL解决过度CPU消耗问题   10.4 使用SQL_TRACE/10046事件进行数据库诊断   10.5 使用物化视图进行翻页性能调整   10.6 一次横跨两岸的问题诊断   10.7 总结
深入解析OracleDBA入门进阶与诊断案例 扫描版 作  者:盖国强 著 出 版 社:人民邮电出版社 出版时间:2009-1-1 页  数:527 内容简介   针对数据库的启动和关闭、控制文件与数据库初始化、参数及参数文件、数据字典、内存管理、Buffer Cache与Shared Pool原理、重做、回滚与撤销、等待事件、性能诊断与SQL优化等几大Oracle热点主题,本书从基础知识入手,深入研究相关技术,并结合性能调整及丰富的诊断案例,力图将Oracle知识全面、系统、深入地展现给读者。   本书给出了大量取自实际工作现场的实例,在分析实例的过程中,兼顾深度与广度,不仅对实际问题的现象、产生原因和相关的原理进行了深入浅出的讲解,更主要的是,结合实际应用环境,提供了一系列解决问题的思路和方法,包括详细的操作步骤,具有很强的实战性和可操作性,适用于具备一定数据库基础、打算深入学习Oracle技术的数据库从业人员,尤其适用于入门、进阶以及希望深入研究Oracle技术的数据库管理人员。 目录 第1章 数据库的启动和关闭   1.1 数据库的启动   1.2 数据库的访问   1.3 数据库的关闭  第2章 控制文件与数据库初始化   2.1 控制文件的内容   2.2 SCN   2.3 检查点(Checkpoint)   2.4 数据库的初始化  第3章 参数及参数文件   3.1 初始化参数的分类   3.2 参数文件   3.3 诊断案例之一:参数文件   3.4 诊断案例之二:RAC环境参数文件  第4章 数据字典   4.1 数据字典概述   4.2 内部RDBMS(X$)表   4.3 数据字典表   4.4 静态数据字典视图   4.5 动态性能视图   4.6 最后的验证  第5章 内存管理   5.1 PGA管理   5.2 SGA管理   5.3 Oracle的内存分配和使用  第6章 Buffer Cache与Shared Pool原理   6.1 Buffer Cache原理   6.2 Shared Pool的基本原理  第7章 重做(Redo)   7.1 Redo的作用   7.2 Redo的原理   7.3 Redo与Latch   7.4 Oracle 9i Redo的增强   7.5 Oracle 10g Redo的增强   7.6 Redo的内容   7.7 产生多少Redo   7.8 Redo写的触发条件   7.9 Redo Log Buffer的大小设置   7.10 commit做了什么?   7.11 日志的状态   7.12 日志的块大小   7.13 日志文件的大小   7.14 如何调整日志文件大小   7.15 为什么热备份期间产生的Redo要比正常的多   7.16 能否不生成Redo   7.17 Redo故障的恢复   7.18 诊断案例一:通过Clear日志恢复数据库   7.19 诊断案例二:日志组过度激活的诊断   附录 数值在Oracle的内部存储  第8章 回滚与撤销   8.1 什么是回滚和撤销   8.2 回滚段存储的内容   8.3 并发控制和一致性读   8.4 回滚段的前世今生   8.5 Oracle 10g的UNDO_RETENTION管理增强   8.6 UNDO_RETENTION的内部实现   8.7 Oracle 10g In Memory Undo新特性   8.8 Oracle 11g UNDO表空间备份增强   8.9 回滚机制的深入研究   8.10 Oracle 9i闪回查询的新特性   8.11 使用ERRORSTACK进行错误跟踪   8.12 Oracle 10g闪回查询特性的增强   8.13 ORA-01555成因与解决   8.14 Oracle 11g闪回数据归档   8.15 AUM下如何重建UNDO表空间   8.16 使用Flashback Query恢复误删除数据   8.17 诊断案例之一:释放过度扩展的UNDO空间   8.18 特殊情况的恢复   8.19 诊断案例之二:回滚段损坏的恢复  第9章 等待事件   9.1 等待事件的源起   9.2 从等待发现瓶颈   9.3 Oracle 10g的增强   9.4 顶级等待事件   9.5 重要等待事件  第10章 性能诊断与SQL优化   10.1 使用AUTOTRACE功能辅助SQL优化   10.2 获取SQL执行计划的方法   10.3 捕获问题SQL解决过度CPU消耗问题   10.4 使用SQL_TRACE/10046事件进行数据库诊断   10.5 使用物化视图进行翻页性能调整   10.6 一次横跨两岸的问题诊断   10.7 总结
 针对数据库的启动和关闭、控制文件与数据库初始化、参数及参数文件、数据字典、内存管理、Buffer Cache与Shared Pool原理、重做、回滚与撤销、等待事件、性能诊断与SQL优化等几大Oracle热点主题,本书从基础知识入手,深入研究相关技术,并结合性能调整及丰富的诊断案例,力图将Oracle知识全面、系统、深入地展现给读者。   本书给出了大量取自实际工作现场的实例,在分析实例的过程中,兼顾深度与广度,不仅对实际问题的现象、产生原因和相关的原理进行了深入浅出的讲解,更主要的是,结合实际应用环境,提供了一系列解决问题的思路和方法,包括详细的操作步骤,具有很强的实战性和可操作性,适用于具备一定数据库基础、打算深入学习Oracle技术的数据库从业人员,尤其适用于入门、进阶以及希望深入研究Oracle技术的数据库管理人员。 第1章 数据库的启动和关闭 1 1.1 数据库的启动 1 1.1.1 启动数据库到NOMOUNT状态的过程 2 1.1.2 启动数据库到MOUNT状态 18 1.1.3 启动数据库OPEN阶段 26 1.2 数据库的访问 37 1.2.1 客户端的TNSNAMES.ORA文件配置 37 1.2.2 服务器端的监听器文件listener.ora配置 39 1.2.3 通过不同服务器名对数据库的访问 41 1.2.4 动态监听器注册服务 42 1.3 数据库的关闭 46 1.3.1 数据库关闭的步骤 46 1.3.2 几种关闭方式的对比 48 第2章 控制文件与数据库初始化 51 2.1 控制文件的内容 51 2.2 SCN 53 2.2.1 SCN的定义 53 2.2.2 SCN的获取方式 53 2.2.3 SCN的进一步说明 54 2.3 检查点(Checkpoint) 57 2.3.1 检查点(Checkpoint)的工作原理 57 2.3.2 常规检查点与增量检查点 59 2.3.3 LOG_CHECKPOINT_TO_ALERT参数 63 2.3.4 控制文件与数据文件头信息 64 2.3.5 数据库的启动验证 66 2.3.6 使用备份的控制文件 70 2.3.7 FAST_START_MTTR_TARGET 71 2.3.8 关于检查点执行的案例 74 2.3.9 Oracle 10g自动检查点调整 75 2.3.10 检查点信息及恢复起点 78 2.3.11 正常关闭数据库的状况 78 2.3.12 数据库异常关闭的情况 80 2.3.13 数据库并行恢复案例一则 82 2.3.14 判断一个死事务的恢复进度 85 2.4 数据库的初始化 86 2.4.1 bootstrap$及数据库初始化过程 86 2.4.2 bootstrap$的定位 88 2.4.3 Oracle中独一无二的Cache对象 89 2.4.4 Oracle数据库的引导 91 2.4.5 系统对象与bootstrap$ 92 2.4.6 bootstrap$的重要性 94 2.4.7 BBED工具的简要介绍 95 2.4.8 坏块的处理与恢复 97 第3章 参数及参数文件 103 3.1 初始化参数的分类 103 3.1.1 推导参数(Derived Parameters) 103 3.1.2 操作系统依赖参数 104 3.1.3 可变参数 104 3.1.4 初始化参数的获取 105 3.2 参数文件 107 3.2.1 PFILE和SPFILE 108 3.2.2 获取参数的视图 110 3.2.3 SPFILE的创建 111 3.2.4 SPFILE的搜索顺序 112 3.2.5 使用PFILE/SPFILE启动数据库 112 3.2.6 修改参数 113 3.2.7 解决SPFILE参数修改错误 118 3.2.8 重置SPFILE中设置的参数 120 3.2.9 判断是否使用了SPFILE 120 3.2.10 SPFILE的备份与恢复 121 3.2.11 Oracle 11g参数文件恢复 127 3.2.12 如何设置Events事件 128 3.2.13 导出SPFILE文件 129 3.3 诊断案例之一:参数文件 131 3.3.1 登录系统检查告警日志文件 131 3.3.2 尝试重新启动数据库 132 3.3.3 检查数据文件 132 3.3.4 MOUNT数据库,检查系统参数 133 3.3.5 检查参数文件 133 3.3.6 再次检查alert文件 134 3.3.7 修正PFILE 135 3.3.8 启动数据库 135 3.4 诊断案例之二:RAC环境参数文件 135 3.4.1 数据库资源异常 135 3.4.2 问题的发现 136 3.4.3 参数文件问题的解决 137 第4
To implement a shared resource pool in C++, you can modify the previous example by introducing shared ownership of resources using smart pointers. Here's an updated version: ```cpp #include <iostream> #include <queue> #include <mutex> #include <condition_variable> #include <memory> template<typename T> class ResourcePool { public: ResourcePool(size_t maxSize) : max_size(maxSize), available_resources(0) {} std::shared_ptr<T> acquire() { std::unique_lock<std::mutex> lock(mutex); while (resources.empty()) { if (available_resources >= max_size) { // Wait until a resource is released cv.wait(lock); } else { // Create a new resource if the pool is not full resources.push(createResource()); ++available_resources; } } std::shared_ptr<T> resource = resources.front(); resources.pop(); return resource; } void release(std::shared_ptr<T> resource) { std::lock_guard<std::mutex> lock(mutex); resources.push(resource); cv.notify_one(); } private: size_t max_size; size_t available_resources; std::queue<std::shared_ptr<T>> resources; std::mutex mutex; std::condition_variable cv; // Create a new resource object (replace with your own code) std::shared_ptr<T> createResource() { return std::make_shared<T>(); } }; // Example usage class MyResource { public: void use() { std::cout << "Using resource" << std::endl; } }; int main() { ResourcePool<MyResource> pool(5); // Acquire and use resources for (int i = 0; i < 10; ++i) { std::shared_ptr<MyResource> resource = pool.acquire(); resource->use(); pool.release(resource); } return 0; } ``` In this updated example, the `ResourcePool` class now uses `std::shared_ptr` to manage shared ownership of resources. When acquiring a resource, a `std::shared_ptr` is returned instead of a raw pointer. This allows multiple threads or parts of the code to safely share and use the resource without worrying about premature destruction. When releasing a resource, a `std::shared_ptr` is passed back to the pool instead of a raw pointer. Note that the `createResource` function now uses `std::make_shared` to create the resource object, which ensures that the object is properly managed by a shared pointer. By using shared pointers, you can safely share and manage resources within the resource pool, allowing multiple parts of your code to use them without concerns about memory ownership and deallocation.

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值