深入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; |
这里的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。
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)。
假设可用的chunk链表(也就是bucket)被分成了254个,每个bucket上挂的chunk的尺寸是不一样的,有一个递增的趋势。同时每个bucket都有一个size字段,这个size就说明了该bucket上所能链接的可用chunk的大小尺寸。
当
某个进程需要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的速度很快,因此叫轻量级锁。
从一个逻辑层面来看,shared pool由library cache和dictionary cache组成。
,
当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。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/8113358/viewspace-617411/ ,如需转载,请注明出处,否则将追究法律责任。