Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1128060
  • 博文数量: 276
  • 博客积分: 10077
  • 博客等级: 上将
  • 技术积分: 2513
  • 用 户 组: 普通用户
  • 注册时间: 2007-08-24 20:31
文章分类

全部博文(276)

文章存档

2020年(1)

2015年(5)

2012年(2)

2011年(6)

2010年(7)

2009年(224)

2008年(31)

我的朋友

分类: Oracle

2009-02-03 08:55:35

摘要:本文首先详细介绍了oracle中shared pool的概念以及所包含的内存结构。然后深入介绍了oracle对于shared pool的管理机制。最后全面介绍了有关buffer cache监控以及调优的实用方法。

  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.

 

阅读(786) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~