Shared pool
一、Shared pool的引入
当你发出一条sql语句交付Oracle,在执行和获取结果前,Oracle对此sql将进行几个步骤的处理过程:
1、语法检查(syntax check)
检查此sql的拼写是否语法。
2、语义检查(semantic check)
诸如检查sql语句中的访问对象是否存在及该用户是否具备相应的权限。
3、对sql语句进行解析(prase)
利用内部算法对sql进行解析,生成解析树(parse tree)及执行计划(execution plan)。
4、执行sql,返回结果(execute and return)
Oracle早期版本,并不存在shared pool,每次sql执行时,都会对sql进行以上解析执行操作。但是,在第三步解析、生成执行计划的过程中,开销非常昂贵,所以,oracle引入shared pool。
当一条sql语句到达数据库时,Oracle利用内部的hash算法来取得该sql的hash值,然后在library cache里查找是否存在该hash值; 假设存在,则将此sql与cache中的进行比较;
假设"相同",就将利用已有的解析树与执行计划,而省略了优化器的相关工作。这也就是软解析的过程。 如果hash值不存在,需要到Free空间中,找到相应大小的内存块,进行创建解析树、生成执行计划的动作。这个过程就叫硬解析。 创建解析树、生成执行计划对于sql的执行来说是开销昂贵的动作,所以,应当极力避免硬解析,尽量使用软解析。 这就是在很多项目中,倡导开发设计人员对功能相同的代码要努力保持代码的一致性,以及要在程序中多使用绑定变量的原因。
Shared Pool最初被引入的目的,也就是它的本质功能在于实现共享。如果sql没有共享,有shared pool不如没有。因为保存代码、执行计划等期待重用,有一定的系统开销,并且客户端要不停的获取Latch,试图寻找共享代码。如果没有sql及执行计划的共享,这种开销会成为ORACLE的负担。
二、Shared Pool的组成
Shared Pool主要由三部分组成,一部分是库缓存(Library Cahce),另一部分是数据字典缓存(Data Dictionary Cache),在ORACLE中也叫ROW Cache,最后一部分,Free空间。
Library Cache主要用于存储SQL语句、SQL语句相关的解析树、执行计划、PL/SQL程序块(包括匿名程序块、存储过程、包、函数等)以及它们转换后能够被Oracle执行的代码等。由于Library Cache在解析的过程,需要频繁的访问到数据库的字典信息,如表结构,表行数等,所以,将字典值缓存也放到shared pool中。字典缓存的内容是按行(Row)存储的(其他数据通常按Buffer存储),所以又被称为Row Cache。Free空间,为一个个小的内存块,使用chain连接起来。
shared pool从物理结构上来看,是由许多内存块组成。这些内存块称之为chunk。 Chunk是shared pool中内存的最小单位。 Chunk中所有的内存都是连续的。
在shared pool中可用的chunk会串起来成为可用链(free list),也称之为bucket.可用的bucket,被分为254个。当一个进程需要shared pool里的一个chunk时,该进程首先会在符合所需空间的bucket上扫描,找到一个合适的chunk。如果找的chunk尺寸比需要的尺寸要大,该chunk就会被拆分成2个chunk,一个chunk用来存放数据,一个则成为free类型的chunk并被挂在当前的该bucket。
如果当前bucket上没有符合chunk,就会从下一个非空bucket里找,所里的bucket都不符合,就会扫描已经使用的recreatable类型的chunk的链表。从该链表上释放一部分的chunk.
当shared pool找不到足够大小的所需内存的时候,就会发出ora-4031的错误信息。当我们查询v$sgastat里的可用空间时,可能发现name 为free memory可用空间还足够大,但是还是报错,是因为在发出ora-4031错误的时候,已经释放不少内存。但是在这些内存当中的chunk,没有一个chunk可以提供连续的的物理内存满足所需要的空间。
三、Shared Pool 相关概念
(1)CHAIN 链
串接内存块(),方便遍历。Oracle内存结构中,最大的特征,就是使用了大量的Chain链,使用锁(Latch)对链进行保护。
(2)chunk 内存块
(3)Free空间的内存结构
Free空间 ,由许多chain组成,这些chain根据内存块chunk的大小,进行串接,当需要使用时,可以遍历到合适的内存块,放入sql及执行计划,然后挂到Library Cache的chain上。
(4)Library Cache中的内存结构
Library Cache的chain,是按照sql语句的hash值来串接的,如果一个sql到oracle中执行,会首先算出这个sql的hash值,再根据这个值,来Library Cache中查找是否有对应的内存块,有,就是软解析,没有,则需要硬解析。
(5)x$ksmsp 视图
Shared pool每一个chunk,都在这个表中,有一条记录。
四、ORA-4031经典错误
产生的原因
ORA-4031错误的原因,一般是大量的hard parse导致了shared pool中的free list中产生大量的内存小碎片,当一个需要很大内存来进行hard parse的sql语句到来时,无法从free list中找到内存,即使进行内存的释放,还是不能找到符合的内存块。从而报ORA-4031错误。
解决办法
(1)Alter system flush shared_pool;治标不治本的解决4031错误。之后一段时间,大量存在硬解析。
(2)减少硬解析,是解决问题的关键。在前台调用时,采用绑定变量的方式,来减少硬解析。查询执行1次的sql语句,很有可能就是硬解析的sql。需要优化。
(3)保留区:当sql需要的chunk非常大时,不去Free空间找chunk,直接从保留区找空间。把保留区的空间设置大一点,避免4031错误。
五、Shared Cursor
Oracle里的cursor分为两种:一种是shared cursor,一种是session cursor。shared cursor就是指缓存在library cache里的一种library cache object,说白了就是指缓存在library cache里的sql和匿名pl/sql。当一条sql第一次被执行的时候,会同时产生parent cursor和child cursor,parent cursor的library cache object handle的heap 0里会存储其child cursor的地址,这个sql的执行计划会存储在上述child cursor的library cache object handle的heap 中,第一次执行上述sql所产生的parent cursor和child cursor的过程也就是所谓的"硬解析"主要做的事情。如果上述sql再次执行,Oracle只需要去扫描相应的library cache object handle,找到上次硬解析后产生的child cursor,把里面的parse tree和执行计划直接拿过用就可以了,这就是所谓的"软解析"。
六、Shared pool大小设置
shared pool是不是越大越好?答案是否定的。第一、当sql和执行计划没有实现共享的时候,ORACLE会因为有shared pool 的存在,而增加很多系统开销。这个时候,shared pool有等于没有。第二,如果shared pool设置很大,那么对应的Library Cache也会很大, cahce中的chain也会很大。当chain进行便来的时候,chain就会经常锁住,导致系统运行缓慢。在oracle 10g之前,shared pool大小一般不允许超过1g。但是,10g之后,引入了sub sharedpool的概念,sub shared pool多了之后,能保证chain不会过大,导致大量的锁,这样,shared pool能设置很大。
七、ROW Cahce
dictionary cache存放了在执行SQL语句过程中,所参照的数据字典的信息,包括SQL语句所涉及的表名、表的列、权限信息等。dictionary cache也叫做row cache,因为这里面的信息都是以数据行的形式存放的,而不是以数据块的形式存放的。对于dictionary cache来说,oracle倾向于将它们一直缓存在shared pool里,不会将它们交换出内存。
当一条SQL语句进入Library Cache时,oracle会到dictionary cache中找与操作对象有关的字典信息, 比如表明、表的列、用户权限等信息,如果dictionary cache中没有这些信息,将system表空间里的数据字典信息调入buffer cache内存,读取内存数据块的数据字典内容,然后将这些读取出来的数据字典内容按照行的形式加入dictionary cache里,从而构造出dc_tables之类的对象,然后从dictionary cache中的行数据中读取有关的列信息放入library cache中。
八、Library Cache
当一条 SQL 语句进入library cache 的时候,先将 SQL 文本转化为对应ASCII数值,然后对该这些ASCII数值进行 hash 函数的运算,传入函数的是SQL语句的名称以及命名空间。运用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 语句的对象就是书中的页,而句柄就是目录,通过目录可以快速定位到指定内容的页。
Library cache lock/pin
Library cache lock/pin是用来控制对library cache object的并发访问的。Lock管理并发,pin管理一致性,lock是针对于library cache handle,而pin是针对于heap。
当我们想要访问某个library cache object,我们首先要获得这个指向这个object的handle的lock,获得这个lock之后我们就需要pin住指向这个object的heap。
当我们对包,存储过程,函数,视图进行编译的时候,Oracle就会在这些对象的handle上面首先获得一个library cache lock,然后再在这些对象的heap上获得pin,这样就能保证在编译的时候其它进程不会来更改这些对象的定义,或者将对象删除。
当一个session对SQL语句进行硬解析的时候,这个session就必须获得library cache lock,这样其他session就不能够访问或者更改这个SQL所引用的对象。如果这个等待事件花了很长时间,通常表明共享池太小(由于共享池太小,需要搜索free的chunk,或者将某些可以被移出的object page out,这样要花很长时间),当然了,也有可能另外的session正在对object进行修改(比如split 分区),而当前session需要引用那个table,那么这种情况下我们必须等另外的session进行完毕。
Library Cache lock有3中模式:
l Share(S): 当读取一个library cache object的时候获得
l Exclusive(X): 当创建/修改一个library cache object的时候获得
l Null(N): 用来确保对象依赖性
比如一个进程想要编译某个视图,那么就会获得一个共享锁,如果我们要create/drop/alter某个对象,那么就会获得exclusive lock。Null锁非常特殊,我们在任何可以执行的对象(cursor,function)上面都拥有NULL锁,我们可以随时打破这个NULL锁,当这个NULL锁被打破了,就表示这个对象被更改了,需要从新编译。NULL锁主要的目的就是标记某个对象是否有效。比如一个SQL语句在解析的时候获得了NULL 锁,如果这个SQL的对象一直在共享池中,那么这个NULL锁就会一直存在下去,当这个SQL语句所引用的表被修改之后,这个NULL锁就被打破了,因为修改这个SQL语句的时候会获得Exclusive 锁,
由于NULL锁被打破了,下次执行这个SQL的时候就需要从新编译。
九、关注点
(1)软解析的命中率:
Labrary Cache需要在99%以上。
SQL> desc v$librarycache
Name Null? Type
----------------------------------------- -------- ----------------------------
NAMESPACE VARCHAR2(15)
GETS NUMBER 可以理解为某个object解析的时候查找的次数(解析阶段)
GETHITS NUMBER get命中次数
GETHITRATIO NUMBER 这个值等于gethits/gets
PINS NUMBER 某个object 解析过后被执行的次数(发生在执行阶段)
PINHITS NUMBER pin命中次数
PINHITRATIO NUMBER 这个值等于pinhits/pins
RELOADS NUMBER 某个object 解析过后被从新加载的次数(需要从新从磁盘读取object),也就是没有被缓存到library cache中,这个通常由于shared pool 过小
INVALIDATIONS NUMBER 某个对象无效,通常由于对象定义被更改,需要从新解析