库缓存(Library Cache)内存结构

          • -
          • -

Library cache是Shared pool的一部分,它几乎是Oracle内存结构中最复杂的一部分.

一 , Library cache存放什么(存放的信息单元都叫做对象) ?

Library存放的信息单元都叫做对象,这些对象可以分为两类:

1. 存储对象


  1. 过渡对象(游标Cursor,这里的游标指生成的可执行的对象, 运行相同SQL的多个进程可以共享该SQL产生的游标,节省内存。)

A. 用户提交的SQL
B. SQL语句相关的解析树
C. 执行计划
D. 用户提交的PL/SQL程序块(包括匿名程序块,procedure,packages,function等)
E. PL/SQL对象依赖的table,index,view等对象
F. 控制结构:lock,pin,dependency table 等

备注: LIBRARY CACHE的对象可以在V$DB\_OBJECT\_CACHE中找到,这个视图基于X$KGLOB。

二, SQL的解析及游标

SQL在解析阶段主要完成以下步骤 :

  1. 将父游标保存到Library Cache中 (父游标的概念参考后面的说明,这一步其实不包含
    在解析过程中)

先将SQL转化为ASCII数值,然后对这些ASCII数值进行hash函数的运算生成hash value (10g还有唯一的SQL\_ID),运算后匹配library cache里的hash bucket (hash bucket简单来 讲是使用hash算法将进入library cache中的SQL 通过一个类似二维数组来表示,比如t[3][6], 每次查找时通过hash算法算出符合的bucket号,找到对应bucket,比如前面t[3][6]中的3号, 每个bucket后面会挂载所有满足hash算法的object handle, object handle会存储SQL名称 [对于SQL而言就是SQL文本], namespace等) ,再匹配hash bucket上面的handle,也就是句柄, 如果匹配成功,那么去找子游标 (子游标的概念参考后面的说明,找到子游标那么直接执行, 如果子游标被交换出库缓存, 那么通过父游标信息重新构造reload一个子游标) , 如果不成功, 即不存在共享的父游标,就会在库缓存中分配一些内存(Chunk),并将新产生的父游标保存进 库缓存,生成一个handle(对象句柄),挂载hash bucket上。接下来进行硬解析。

2 . 包含VPD(虚拟专用数据库)的约束条件
虚拟专用数据库VPD详细信息见后备注。比如对于HR工资的查询,select salary from emp ; 如果设置VPD, 会隐含加入每个用户各自的账号,只能查看自己的,句子会变成类似: select salary from emp where name='susan' ;

3. 对SQL语句进行文法检查,如果存在文法错误,则退出解析过程
确认sql语句是否正确书写(比如没有写from,select拼写错误等),

4. 到数据字典校验SQL涉及的对象和列是否存在,不存在就退出解析过程,这个过程会加载 Dictionary Cache .

5. 将对象进行名称转换,比如将synonym 转换为实际的对象等。若转换失败则退出解析。

6. 检查发出SQL语句的用户(一般指连接用户)是否有访问SQL中引用的对象的权限,若没有则 退出解析。

7. 逻辑优化 -- 用一定的转换技巧(Transforming Queries,查询转换器),生成语法语义上等同 的新的SQL语句。查询语句的形式会影响所产生的执行计划,查询转换器的作用就是改变查询语 句的形式以产生较好的执行计划。四种常见转换技术:视图合并(View Merging)、谓词推进 (Predicate Pushing)、非嵌套子查询(Subquery Unnesting)和物化视图的查询重写(Query
Rewrite with Materialized Views)。

详细可以参考以下文档及后面备注 :
http://download.oracle.com/docs/cd/B19306\_01/server.102/b14211/optimops.htm#i37745

8. 物理优化 -- 首先,生成与每个逻辑优化产生的sql语句有关的执行计划, 接着, 根据 数据字典找到相关的统计信息或者动态收集的统计信息,计算出与执行计划相关的开销。最后, 选中最低开销的执行计划。涉及大量数学运算,所以这一步最消耗CPU资源。 子游标会在这一步 生成 ,执行计划,绑定变量及执行环境是子游标中的主要内容。

9. 将子游标load到库缓存 -- 首先分配内存(Chunk),然后将共享子游标存储进去,最后将它与父游标 关联,与子游标有关的关键内容是执行计划和执行环境,一旦保存到库缓存,父游标与子游标就可以 分别通过视图v$sqlarea和v$sql被具体化。

v$sql中通过child\_number,hash\_value,address来确定一个子游标,而v$sqlarea通过address和hash\_value可以确定一个父游标; 而从10g过后,通过sql\_id就能确定一个游标; 查找是否有共享的父游标
和硬解析是两个不同的过程,父游标共享与否和硬解析没有直接关系,子游标的共享状态决定软硬解析 。

备注:
----------------------------------------------------------------------------------

Namespace:
使用hash算法对SQL语句对应的ASCII进行运算时,传入函数的参数有SQL语句名称及namespace(可通过v$librarycache查询到各种不同的namespace,对于SQL而言值为"SQL AREA") .

VPD虚拟专用数据库的详细信息:
http://www.oracle.com/technology/global/cn/pub/articles/10gdba/week14\_10gdba.html

SQL Parsing Flow Diagram [ID 32895.1]
https://support.oracle.com/CSP/main/article?cmd=show&type=NOT&id=32895.1

解析过程中的逻辑优化部分的查询转换器 ---

从Oracle 8i开始就有四种转换技术:视图合并(View Merging)、谓词推进(Predicate Pushing)、非嵌套子查询(Subquery Unnesting)和物化视图的查询重写(Query Rewrite with Materialized Views)。

视图合并:如果SQL语句中含有视图,经分析后会把视图放在独立的“视图查询块”中,每个视图会产生一个视图子计划,当为整个语句产生执行计划时,视图子计划会被直接拿来使用而不会照顾到语句的整体性,这样就很容易导致不良执行计划的生成。视图合并就是为了去掉“视图查询块”,将视图合并到一个整体的查询块中,这样就不会有视图子计划产生,执行计划的优良性得到提升。

谓词推进:不是所有的视图都能够被合并,对于那些不能被合并的视图Oracle会将相应的谓词推进到视图查询块中,这些谓词通常是可索引的或者是过滤性较强的。

非嵌套子查询:子查询和视图一样也是被放于独立查询块中的,查询转换器会将绝大多数子查询转换为连接从而合并为同一查询块,少量不能被转换为连接的子查询,会将它们的子计划安照一个高效的方式排列。

物化视图的查询重写:当query\_rewrite\_enabled=true时,查询转换器寻找与该查询语句相关联的物化视图,并用物化视图改写该查询语句。

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

三, 父游标与子游标

部分内容参考:
http://www.oraclefans.cn/forum/showblog.jsp?rootid=5553
http://www.itpub.net/thread-1362874-1-1.html (问题)

在硬解析的过程中,进程会一直持有library cache latch,直到硬解析结束。硬解析过程会为该SQL产生两个游标,一个是父游标,另一个是子游标。

父游标和子游标相关问题的讨论:
http://www.itpub.net/thread-1362874-1-1.html

父游标(parent cursor) ---

当用户A发出一条SQL后,Oracle会根据SQL文本内容生成hash value(10g还有唯一的SQL\_ID),对比库缓存中的hash value, 以便能够快速找到Shared pool中已经存在的相同SQL。如果找不到,则Oracle会为这个SQL创建一个parent cursor和一个child cursor。

父游标里主要包含两种信息:SQL文本以及优化目标(optimizer goal)。从v$sqlarea视图中看到的都是有关父游标的信息,v$sqlarea中的每一行代表了一个parent cursor, 比如SQL文本对应字段SQL\_TEXT, 优化目标(optimizer goal)对应后面的RUNTIME\_MEM,EXECUTIONS,CPU\_TIME, DISK\_READS, BUFFER\_GETS 等等 。

父游标在第一次打开时被锁定,直到其他所有的session都关闭游标后才被解锁。当父游标被锁定的时候它是不能被交换出library cache的,只有在解锁以后才能被交换出library cache,这时该父游标对应的所有子游标也被交换出library cache。

一个CURSOR的结构包括PARENT CURSOR和CHILD CURSOR,每个CURSOR至少包含一个CHILD CURSOR。这个CURSOR通过HASHVALUE来区别,每个PARENT CURSOR至少包含一个HEAP0,里面存放环境、状态和绑定变量的信息。每个PARENT CURSOR至少有一个CHILD CURSOR 。handle其实就是存放的父游标,真正的执行计划是存放在子游标上的,也就是heap6上。

PARENT CURSOR是由一个handle和一个object组成,可以通过在库缓存hash table中的hash value查找到handle, 而object 包含了指向它的每个 "child" cursor的指针 。

V$SQLAREA中version\_count看到一个父游标对应多少个子游标,对应关系是靠hash\_value及adress(SQL文本的地址)联系的,V$SQL中相同SQL文本的不同子游标,hash\_value及adress是相同的,但是子地址child\_address却不一样,这里的子地址实际就是子游标所对应的Heap0的句柄(handel)。 V$SQL中的hild\_number编号从0开始,同样SQL文本(父游标共享)不同的child\_number对应不同的child\_address 。Oracle10g版本下V$SQL中有有3个字段bind\_data,
optimizer\_env , optimizer\_env\_hash\_value 应该是用于决定取哪个子游标的字段。不过9i 中v$sql中没有这些字段,具体如何查找到子游标的参考讨论 :
http://www.itpub.net/thread-1362874-1-1.html

子游标 (Child Cursors) ---

子游标包括游标所有的信息,比如具体的执行计划、绑定变量等。子游标随时可以被交换出library cache,当子游标被交换出library cache时,oracle可以利用父游标的信息重新构建出一个子游标来,这个过程叫reload。 子游标具体的个数可以从v$sqlarea的version\_count字段体现出来。而每个具体的子游标则全都在v$sql里体现。可以使用下面的方式来确定reload的比率:
SELECT 100*sum(reloads)/sum(pins) Reload\_Ratio FROM v$librarycache;

一个父游标可以对应多个子游标。当具体的绑定变量的值与上次的绑定变量的值有较大差异(比如上次执行的绑定变量的值的长度是6位,而这次执行的绑定变量的值的长度是200 位)时或者当SQL语句完全相同,但是所引用的对象属于不同的schema时,或执行SQL的环境不同(优化器模式不一样), 都会创建一个新的子游标。

关于子游标新建和reload 的区别,如果所有版本的子游标都不能被共享,那么会创建一个新的子游标 (new create) ,这种情况指的就是 environment 或bind var 长度不一样等 导致的情况 ;而reload 指的是父游标可以共享, 同样的子游标 (environment 或bind var 等都一样)原来已经存在于library cache, 因为某种原因被aged out出去,而现在需要它了,要重新装载回来。

每个child cursor也是由一个handle和一个object构成. child object 又是由两个heap即heap0及heap6 组成,其中Heap0包含所有的针对SQL语句每个版本的标示信息(比如Environment, Statistics, Bind Variables等,比如绑定变量值不同的长度可能导致sql解析版本的不同; Child cursors are also called versions. ),Heap6包含执行计划 。

Child cursor包含了SQL的metadata,也就是使这个SQL可以执行的所有相关信息,如OBJECT和权限,优化器设置,执行计划等。v$sql中的每一行表示了一个child cursor,根据hash value和address与parent cursor 关联。child cursor有自己的address,即V$SQL.CHILD\_ADDRESS。如果有多个child cursor,则表示parent cursor有多个版本,v$sqlarea中的version\_count字段就会纪录下来。

每种类型的dml语句都需要如下阶段:

Create a Cursor 创建游标
Parse the Statement 解析语句
Bind Any Variables 绑定变量
Run the Statement 运行语句
Close the Cursor 关闭游标

四, 硬解析与软解析,"软软"解析,RELOAD

硬解析 ---
首先了解父游标共享的条件 :

1. 字符级的比较, 要求文本完全一致
SQL语句必须完全相同,select * from emp; 和select * from emp; 是不一样的。不能共享。

2. 必须使用相同名称的绑定变量(其实就是文本字符不一致),比如
select age from pepoo where name=:var\_p
select age from pepoo where name=:var\_f
(即使在运行的时候赋予这两个不同名称的绑定变量一样的值,也不能通向父游标)

从SQL解析过程可以看出,父游标是否共享是发生在硬解析之前,所以父游标是否能共享和硬解析没有关系,不过父游标不能共享则一定是硬解析,硬解析的整个过程见上面的第二节 。但是父游标共享了不一定就是软解析。能否避免硬解析,还要看子游标 。

---------------------------------------------------------
父游标共享已经讨论过,这里讨论子游标共享的几种情况 (假设CURSOR\_SHARING=EXACT ):

第一种是A发出的原始SQL语句和其他用户B之前发出的SQL文本一模一样,父亲游标可以共享,但是因为优化器环境设置不同( OPTIMIZER\_MISMATCH), 绑定变量的值的长度在第二次执行的时候发生显著的变化(BIND\_MISMATCH) , 授权关系不匹配(AUTH\_CHECK\_MISMATCH ) 或者 基础物件转换不匹配(TRANSLATION\_MISMATCH) 等导致子游标不能共享,需要新生成一个子游标 。 这与SQL共享(即游标共享)是有关系的 。 这种情况下的执行计划可能不同,也可能相同(我们可以通过plan\_hash\_value看出);
这里因为除SQL TEXT之外的其他条件不符合,所以reload 也不会发生 。子游标就是new create and load,应该是硬解析 。具体的mismatch可以查询 V$SQL\_SHARED\_CURSOR . ;

例如:

--窗口1执行

sys/SYS>alter session set optimizer\_mode=all\_rows;
Session altered.

sys/SYS>select * from tt;
no rows selected

sys/SYS>alter session set optimizer\_mode=first\_rows\_10;
Session altered.

sys/SYS>select * from tt;
no rows selected

--窗口2执行

sys/SYS>select hash\_value,sql\_text,executions,VERSION\_COUNT from
v$sqlarea where sql\_text like '%from tt';

HASH\_VALUE SQL\_TEXT EXECUTIONS VERSION\_COUNT
---------- ---------------------------------------- ---------- -------------
3762890390 select * from tt 2 2

sys/SYS>select HASH\_VALUE,child\_number,sql\_text from v$sql where sql\_text like '%from tt';

HASH\_VALUE CHILD\_NUMBER SQL\_TEXT
---------- ------------ ----------------------------------------
3762890390 0 select * from tt
3762890390 1 select * from tt

可以看到,SQL文本是完全相同的,所以两个子游标共享了一个父游标。但是由于optimizer\_mode的不同,所以生成了2个子游标。如果产生了子游标,那么说明肯定产生了某种mismatch,如何来查看是何种原因产生了mismatch,要通过v$sql\_shared\_cursor。

sys/SYS>select kglhdpar, address,auth\_check\_mismatch, translation\_mismatch,OPTIMIZER\_MISMATCH
2 from v$sql\_shared\_cursor
3 where kglhdpar in
4 ( select address
5 from v$sql
6 where sql\_text like '%from tt');

KGLHDPAR ADDRESS A T O
-------- -------- - - -
89BB8948 89BB83CC N N N
89BB8948 89BB5E78 N N Y

可以看到OPTIMIZER\_MISMATCH列第二行的值为Y,这说明了正是由于optimizer\_mode的不同而产生了子游标。最后,父游标和子游标的意义何在?其实一切都是为了共享。以减少再次解析的资源浪费。

第二种是A发出的原始SQL语句和与在shared pool 中的SQL文本一模一样,父游标可以共享,子游标不存在所谓的mismatch , 目前也存在于库缓存中,可以共享子游标,那么应该是软解析 。

第三种,父游标可以共享, 不同的是,子游标本来是可以共享的,但是目前被交换出(aged out)库缓存,这时会reload 子游标,也就是利用父游标的信息重新构造出一个子游标 ,Oracle已经知道应该共享哪个子游标,只是它暂时被交换出库缓存, reload应该不属于硬解析,是否属于软解析呢 ?虽然被aged out 出库缓存,但是可能某个地方会记录这个子游标的一些信息,而不需要重新生成子游标的相关信息(比如执行计划等), 而只需要reload (reload的具体过程是什么还需要研究) 。

查找是否有共享的父游标和硬解析是两个不同的过程,父游标共享与否和硬解析没有直接关系, 子游标的共享状态决定软硬解析 。

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

从性能的角度来看,尽量避免硬解析,为什么?
第一: 因为逻辑优化(Transforming Queries)和物理优化(选择最优执行计划)都非常依赖CPU的操作。
第二: 需要分配内存来将父游标与子游标保存到库缓存中。由于库缓存是在所有的会话之间共享,
库缓存中的内存分配必须是串行执行。

软解析,"软软"解析 ---

软解析是相对于硬解析而言的,其实只要在hash bucket里可以匹配对应的SQL文本(算一次get),那么就是软解析,说明之前运行过该sql,其实sql执行期间只要一个或多个步骤可以跳过,那么我们就可以定位为软解析。如果这个SQ语句没有被找到,就进行硬解析。软解析有三种类型:

A. 第一种是某session发出的SQL语句与在library cache里其他session发出的SQL一致,父游标和子游标都可以共享,逻辑优化(Transforming Queries),和物理优化(选择最优执行计划)及将这些信息装载到库缓存的heap中 这几个步骤可以省略,表名,列名,名称转化及权限检查还是需要的。

B. 第二种是某session发出的SQL是该session之前发出的曾经执行过的SQL。这时,解析过程只需要进行文法检查及权限检查。

C. 第三种是当设置了session\_cached\_cursors时,当某个session第三次执行相同的SQL时,则会把该SQL的游标信息转移到该session的PGA中。这样,该session以后再执行相同的SQL语句时,会直接从PGA里取出执行计划,跳过硬解析的所有步骤,这是最高效的解析方式,但是会消耗很大的内存。俗称为"软软"解析 。

Reload ---

关于子游标新建和reload 的区别,如果各版本的子游标都不能被共享,那么会创建一个新的子游标 (new create) ,这种情况指的就是 environment 或绑定变量长度不一样等 导致的情况。 而reload 指的是父游标可以共享,同样的子游标 (执行计划,environment 或bind var 等都一样)原来已经存在于library cache, 因为某种原因被aged out出去,而现在需要它了,要重新装载回来 (Oracle数据库可能在某个地方保存了原来相同的子游标信息)。

在Hash bucket中查找SQL,如果有的话就算作是一次get,并查找这个SQL语句的执行计划,如果执行计划已经不存在了(age out)或者是存在但不可用(Invalidation),那么就必须对这条sql语句重新装载,这就叫reload,如果执行计划存在并且可用的话,oracle就执行这句话,这就叫做execution

五, 绑定变量(Bind Variables)

优点: 共享游标,减少硬解析

绑定变量分级 --

前面说到执行环境的变化比如绑定变量定义的类型大小不同会导致生成不同的游标,为了使游标的数量不至于太多,产生了这个功能。此功能将变量的长度分为4个级别,0-32字节,33-128字节,129-2000字节,>2000字节 这四个等级。不用说,同一个绑定变量(长度)的变化,最多能生成4个游标。

缺点: 绑定变量也有缺点。缺点就是,相对于字面量而言,会减弱查询优化器的功能。

比如:
select count(*) from t where id > 10;
select count(*) from t where id > 99999;
根据id值10,99999和表的统计信息,查询优化器可能会选择全表扫描或者索引扫描,是合理的。

使用了绑定变量,优化器会忽略他们的具体值,从而生成相同的执行计划。为了解决这个问题,
oracle9i引入了绑定变量窥测(bind variable peeking)的功能。

绑定变量窥测的优点,就是窥测绑定变量的值,把它们当做字面量来使用。这样的好处,就是能获得最优查询路径,比如是选择全表扫描还是索引扫描。

绑定变量窥测也有缺点,即生成的执行计划依赖第一次生成执行计划时所提供的值。举例来说,就是如果第一次是全扫描,以后永远都是全表扫描了。这个方法对于非OLTP系统的缺点非常明显,因为一个绑定变量集可能返回的结果集只包含几百行的数据,而另一套绑定变量可能返回几百万行数据,因此,Oracle建议保留CURSOR\_SHARING作为该初始化参数的默认值,以强制产生一个新的更有效的执行计划 (cursor\_sharing的详细解释见后面)。

那么如何避免这个缺点呢?只有升级到oracle11g了。
oracle11g引用一个新功能,自适应游标共享(ACS)。这个功能就是根据绑定变量的值,可以为相同的sql语句,生成不同子游标,及不同的执行计划。ACS使用了两个新的度量机制:绑定敏感度和绑定感知。具体可以参考Oracle11g文档。

什么时候不使用绑定变量?
批量任务处理,报表生成,运用OLAP的数据仓库,因为这种大型的查询时间较长,一次新的硬解析相对于这个查询时间不算什么, 所以不用绑定变量没有什么影响 。如果使用绑定变量,10g或以前的版本,一旦第一次执行时绑定变量第一次提供的值如果是小范围的,那么可能是索引扫描,但是第二次可能是数据仓库典型的大时间范围的查询,需要全表扫描,但是还是沿用了前面的索引扫描,这样导致性能下降。OLTP类型大多数是小量密集的操作,所以使用绑定变量时相对最优的执行计划比较稳定 。

在我们不使用where等条件判断时我们就要尽量使用绑定变量(比如普通insert操作),没理由不使用绑定变量; 而涉及到基数选择性判断时我们应该尽量避免使用绑定变量,因为在物理优化阶段的绑定变量窥测遇到较大负面风险。

也可以参考下面的两种建议:

如果sql处理的数据较少, 解析时间显然比执行时间多很多了,那么我们应该尽量使用绑定变量,这种适用于 OLTP(联机事务处理系统);
而如果是数据仓库类型的数据库,我们对绑定变量的使用就应该慎重了,因为这时的执行时间有可能远远大于解析时间,解析时间相对于执行时间近乎可以忽略,所以这时应该尽量不使用绑定变量。

参数CURSOR\_SHARING ---

oracle是为了满足一些以前开发的程序,里面有大量的相似的statement,没有很好的使用绑定变量,但是重写有不现实的情况下使用的一个参数。并且oracle也不建议修改这个参数。保持默认即可。

语法 CURSOR\_SHARING = { SIMILAR | EXACT | FORCE } ,默认值为 EXACT

EXACT --
仅仅允许绝对一样的SQL语句共享同样的游标。当一个SQL语句解析的时候,首先到shared pool区查看是否有完全一样的语句存在,如果不存在(其实此时是找不到共享的父游标),就执行hard parse .

SIMILAR --
如果在shared pool中无法找到完全一样的语句的时候,就会在shared pool进行一次新的查找,就是查找和当前要解析的语句相似的SQL语句。 similar语句就是除了value of some literal不同,别的地方都相同的语句。比如下面:
select * from a where age=2;
select * from a where age=5;
如果在shared pool中查找到这样的语句,就会做下一步的检查,看shared pool中缓存的这个语句的execution plan是否适合当前解析的语句,如果适合,就会使用shared pool的语句,而不去做hard parse。

FORCE --
强制将字面值不一样的但是其他方面是一样的SQL语句共享游标。如果cursor\_sharing设置为force的时候,当在shared pool中发现了similar statement之后,就不会再去检查执行计划了,而直接使用在shared pool中的这个语句了。

将cursor\_sharing设置为force实际上是危险的。这会可能形成suboptimal的执行计划。比如对于一个范围查找的语句,比如select * from a where a>10 and a<20这样类型的语句,缓存中的语句的执行计划可能对于正在解析的语句就是不适合的,不是最优的执行计划。这样看起来是减少了解析的时间,但是大大增大了execution的时间。

什么时候需要修改这个参数呢?需要满足以下的条件:
一个是由于大量的shared pool hitmis影响了用户的响应时间(就是当前的shared pool无法满足共享sql语句存储的需要),如果没有这个问题,那么设置这个参数,可能会造成更糟糕的性能。这个参数仅仅只是减少parse的时间。另外一个就是在现有程序中有大量的similar statement,可通过设置这个参数来获得相对比较好的性能。

---------------------------------------------------------------
关于cursor\_sharing = similar的测试 :
http://www.wangchao.net.cn/bbsdetail\_60551.html

若存在object\_id的 histograms ,则每次是不同的 值 的时候都产生硬解析 ,若不存在 histograms ,则不产生硬解析 。换句话说,当表的字段被分析过存在histograms的时候,similar 的表现和exact一样,当表的字段没被分析不存在histograms的时候,similar的表现和force一样。这样避免了一味地如force一样转换成变量形式,因为有hostograms的情况下转换成变量之后就容易产生错误的执行计划,没有利用上统计信息。而exact呢,在没有histograms的情况下也要分别产生硬解析,这样的话,由于执行计划不会受到数据分布的影响(因为没有统计信息)重新解析是没有实质意义的。而similar则综合了两者的优点。

备注: cursor\_sharing=force or similar时,在9205以下的版本BUG不少 。
---------------------------------------------------------------

Library cache内部机制详解 参考:

http://www.hellodba.net/2010/07/oracle-library-cache.html

          • -
          • -

共享池部分-library cache、library cache object handle、library cache object、shared cursor、session cursor和解析一些基本概念

Posted on 2015 年 1 月 7 日 by xiaoyu

关于cursor是oracle中开发人员经常使用的,这里我们不提开发人员所提的cursor,而是简单提下shared pool中的parent cursor和child cursor以及pga中的session cursor,以及硬解析、软解析、library cache的内部结构

shared pool中的library cache结构:

  1. library cache中的对象称为library cache object,而library cache object都是以library cache object handle的结构存储在library cache中,要访问library cache object,就需要先访问library cache object handle,library cache object handle就是oracle一种自定义的c语言复杂结构,那么oracle是如何来存储和访问library cache object handle

实际上,library cache object handle是以hash table的方式存储在library cache中,每一个hash bucket都对应不同的hash value,对于单个hash bucket而言,里面存储的就是哈希值相同的所有library cache object handle,同一个hash bucket中不同的library cache object handle之间会用指针连接起来,即同一个hash bucket中不同的library cache object handle组成该hash bucket中涉及的library cache object的库缓存对象句柄链表(library cache object handles)。

库缓存(Library Cache)内存结构教程

oracle如何去解析sql语句、软解析和硬解析:

  1. 当oracle要执行目标sql语句时,首选对该sql的sql文本做hash运算,然后根据得到的hash值去相关的hash bucket中遍历对应的库缓存对象句柄链表,此时需要持有library cache latch,如果找到了对应的library cache object handle,则可以直接使用其执行计划、解析树等对象,最后释放library cache latch,这就是我们常说的软解析,如果没有找到对应的library cache object handle,需要再次持有library cache latch,由于有新增加的sql对应的shared cursor(parent cursor和child cursor)需要load到shared pool中,shared pool结构发生了变化,oracle需要在shared pool中加载和分配新的空间,oracle会在持有library cache latch的前提下再持有shared pool latch,成功申请空间后释放掉shared pool latch,然后重新解析sql并把相关的sql执行计划、解析树等对象以library cache object handle的方式存储到先关的hash bucket中的库缓存对象句柄链表中,最后再释放library cache latch,这个也就是凑名昭著的硬解析。

查看上面软解析和硬解析的过程,我们发现硬解析会较长时间的持有library cache latch,也会持有shared pool latch,虽然软解析也会持有library cache latch,但是持有次数相比硬解析要少,持有时间也会硬解析短,软解析不会持有shared pool latch;如果系统出现大量的硬解析,则会引起library cache latch和shared pool latch的争用,一般在OLTP系统中,经常在书中看见要我们更多的使用绑定变量来提高系统的性能的原因,在oracle 11g后,oracle用mutex替换了相关的library cache latch,mutex的作用除了有latch的作用,还有library cache pin的作用。

上面讲述了硬解析、软解析、library cache的结构后,再来简单提下经常看见parent cursor和child cursor

  1. 碰见太多的oracle文档中提到的shared pool主要是library cache和row cache,而library cache最主要的就是缓存sql语句,如果直接点就是缓存cursor,这里的cursor包括两种parent cursor和child cursor,那么parent cursor是什么了,child cursor又是什么了,这里需要对library cache object handle做一些深入的了解,library cache object handle也就是库缓存句柄有很多的属性,而对于cursor的library cache object handle而言比较重要的就是name、namespace和heap 0 pointer

属性“Name”:表示的时library cache object handle所对应的library cache object的名称,例如如果是sql语句对应的库缓存对象句柄,则属性name就是该sql语句的sql文本;如果是表对应的库缓存对象句柄,则属性name就是该表的表明

属性“Namespace“:表示的是库缓存对象句柄对应的库缓存对象所在的分组名,不同类型的库缓存对象句柄可能属于同一个分组,比如sql语句和pl/sql语句所对应的库缓存对象句柄的namespace值都是CRSR

属性 “heap 0 pointer”:这里要说明下library cache object handle类似c语言的结构体,library cache object handle中还嵌套了一些子结构,其中heap 0 pointer是指向子结构heap 0的指针

heap 0也是一种复杂的结构,它一样有很多属性,对于cursor而言比较重点的两个属性是tables和data block pointer。

属性table记录的是于该heap 0所在的库缓存对象有关联关系的库缓存对象句柄地址的集合,table又细分为很多类,对于cursor而言重点关注child table,child table记录的就是从属于该heap 0所在的库缓存对象的子库缓存对象句柄地址的集合,简单来讲可以理解为指向子游标的库缓存对象句柄的指针,那么oracle就可以直接通过访问库缓存对象(parent cursor)句柄的heap 0中指定的child table,来直接找到相关的子库缓存对象(child cursor)句柄,进而访问子库缓存对象。
属性data block pointer是heap 0存储的指向data heap的指针,data heap可以简单理解为库缓存中的一块连续的内存区域,而这些内存区域就是存储着cursor的动态运行时runtime数据,比如特别常见的执行计划、sql所涉及的对象定义、绑定变量类型和长度等。

库缓存(Library Cache)内存结构教程

parent cursor和child cursor都属于shared pool中shared cursor,parent cursor和child cursor都是以library cache object handle的方式存储在library cache中,parent cursor存储了目标sql的sql文本,其中sql文本是存储在parent cursor所对应的library cache object handle的属性name中,而child cursor则存储了执行计划和解析树。

  • oracle在解析目标sql去library cache中查找是否有匹配的shared cursor过程大体如下:

2 找到合适的hash bucket后,就去这个hash bucket对应的library cache object handles链表中找到是否有满足条件的library cache object handle(这里需要检验sql文本或者pl/sql文本是否一致,因为不同的sql语句或者pl/sql语句的hash value可能一致)

3 根据找到的library cache object handle也就是parent cursor对应的库缓存对象句柄去找对应的child cursor,最后看是否满足重用执行计划和解析数(要比对对象、绑定变量类型和长度等)

  • 1 首先是根据库缓存对象句柄的name和namespace做hash运算,由于sql语句和pl/sql语句的namespace都是crsr,对于sql和pl/sql语句可以直接理解为根据sql text来做hash运算,得到了hash value就去hash bucket中找到是否有匹配的hash bucket

parent cursor和child cursor遍历顺序:

  1. child cursor的name属性是空的,也就是我们不可能直接脱离parent cursor而去寻找child cursor,查看sql是否可以重用时都是需要先找到parent cursor然后根据parent cursor对应的库缓存对象句柄中heap 0 pointer-heap 0-table-child tables来去cursor cursor对应的库缓存对象句柄链表中去遍历是否有满足的cursor child(此时不仅仅需要验证sql文本一致,文本一致只作用于验证parent cursor部分是否可以共享,还要查看sql所涉及的对象定义、绑定变量类型和长度、优化器参数等来看是否能够重用child cursor)

那么对于shared pool而言硬解析就有以下两种情况: 1 parent cursor和child cursor都没有办法共享 2 parent cursor能够共享,但是child cursor不能共享

可能在刚开始我们第一次接触oracle部分书籍和文档时就提到了shared pool中的library cache,但是真正能够讲清楚shared cursor、child cursor、hash bucket、library cache、library cache object、软解析、硬解析得并不多,当然这些概念模棱两可并不太影响我们运维的工作,但是如果能真正理解这些概念是可以帮助我们更深入的理解oracle的sql解析部分

下面再来简单讲一个比较边缘化的概念session cursor:

  1. session cursor顾名思义就是session级别的cursor,和shared cursor一样,session cursor也是oracle定义的一种c语言复杂结构,也是以hash表的方式缓存起来,所不同的是session cursor是缓存在PGA中。

跟shared cursor所不同的还是有以下几点:

  • 1 session cursor是于session相对应,不同的session之间的session cursor没有办法共享
    2 session cursor也是有生命周期的
    3 session cursor也是以hash表的形式缓存在pga中,oracle也是通过相关的hash运算先访问当前session的pga的session cursor。

由于session cursor概念的引入,oracle的解析需要增加这一步骤:首先计算hash value查找session cursor中是否有满足条件的session cursor,如果有满足条件的session cursor,则重用这个session cursor,如果没有满足条件的session cursor,则需要重新生成一个session cursor,对于session cursor而言,这个也属于硬解析,如果session cursor、parent cursor、child cursor都已经分别存在了PGA和library cache中,则这就是我们所提到的软软解析,软软解析不需要再次生成session cursor,相比软解析消耗更少的资源和时间。

软软解析的必须条件:

  1. 软软解析需要shared cursor和session cursor都分别在shared pool和pga中缓存,而将session cursor缓存到pga跟sql的执行解析次数和session\_cached\_cursors参数有关:如果session\_cached\_cursors等于0,那么session cursor在open、parse、bind、execute fetch后就直接close了。而如果session\_cached\_cursor大于0,而且session cursor所对应的sql解析和执行次数超过了3次,这个session cursor就能够被pga缓存住(存储在pga中的session cursor是用lru算法来管理的),这样sql再次执行时,shared cusror和session cursor都能够找到匹配的记录,oracle不再重复的需要为其生成一个session cursor,当然close一个缓存在pga的session cursor,也只是需要将其标记为soft cloud。

接下来我们来验证下session cursor缓存到pga的条件:

SQL> show parameter session\_cached\_cursors;

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
session\_cached\_cursors integer 50

SQL> select count(*) from t\_samp01;

COUNT(*)
----------
0

SQL> select cursor\_type,sql\_id from v$open\_cursor where sid=50 and sql\_text like 'select count(*) from t\_samp01%';

no rows selected

SQL> select count(*) from t\_samp01;

COUNT(*)
----------
0

SQL> select cursor\_type,sql\_id from v$open\_cursor where sid=50 and sql\_text like 'select count(*) from t\_samp01%';

no rows selected

SQL> select count(*) from t\_samp01;

COUNT(*)
----------
0

SQL> select cursor\_type,sql\_id from v$open\_cursor where sid=50 and sql\_text like 'select count(*) from t\_samp01%';

CURSOR\_TYPE SQL\_ID
---------------------------------------------------------------- -------------
DICTIONARY LOOKUP CURSOR CACHED 16099tj19y4gr

SQL> select count(*) from t\_samp01;

COUNT(*)
----------
0

SQL> select cursor\_type,sql\_id from v$open\_cursor where sid=50 and sql\_text like 'select count(*) from t\_samp01%';

CURSOR\_TYPE SQL\_ID
---------------------------------------------------------------- -------------
SESSION CURSOR CACHED 16099tj19y4gr
SQL> select sql\_text from v$sql where sql\_id='16099tj19y4gr';

SQL\_TEXT
--------------------------------------------------------------------------------
select count(*) from t\_samp01

看见同一个session下执行3次以上的sql对应的session cursor已经缓存到了pga中

在oracle 11g之前的办法,在缓存session cursor的hash表对应的hash bucket中,oracle会缓存目标sql对应的parent cursor的库缓存对象句柄地址,这意味着oracle已经建立起了session cursor到parent cursor的联系,这样软软解析会对相关的latch的争用更低,因为session cursor中存储了直接指向parent cursor的指针,oracle不再需要和软解析一样要去持有library cache latch访问相关的hash bucket的library cache object handles链表中查找匹配的parent cursor

在oracle 11g后,dump session的信息,session cursor中已经没有记录,对应的parent cursor的library cache object handle地址:
11.2.0.3版本下alter sssion set events ‘immediate trace name errorstack level 3’的trac文件session cached cursor的dump中已经不存储指向parent cursor中的library cache object handle地址

11.2.0.3版本的trace文件:
----- Session Cached Cursor Dump -----
----- Generic Session Cached Cursor Dump -----
-----------------------------------------------------------
-------------- Generic Session Cached Cursors Dump --------
-----------------------------------------------------------
hash table=0x7feac43941d0 cnt=6 LRU=0x7feac4386678 cnt=6 hit=10 max=50 NumberOfTypes=6
type#0 name=DICTION count=0
type#1 name=BUNDLE count=5
type#2 name=SESSION count=1
type#3 name=PL/SQL count=0
type#4 name=CONSTRA count=0
type#5 name=REPLICA count=0
Bucket#024 seg=0x7feac4394648 nit=5 nal=5 ips=5 sz=56 flg=3 ucnt=1
0 cob=0x7feac43021f8 idx=18 flg=0 typ=1 cur=0x7feac4302328 lru=1 fl=15
Bucket#056 seg=0x7feac4394c48 nit=5 nal=5 ips=5 sz=56 flg=3 ucnt=1
0 cob=0x7feac42db1e8 idx=38 flg=0 typ=1 cur=0x7feac42db318 lru=1 fl=15
Bucket#130 seg=0x7feac4395a28 nit=5 nal=5 ips=5 sz=56 flg=3 ucnt=1
0 cob=0x7feac43a1f78 idx=82 flg=0 typ=1 cur=0x7feac43a20a8 lru=1 fl=15
Bucket#183 seg=0x7feac4396418 nit=5 nal=5 ips=5 sz=56 flg=3 ucnt=1
0 cob=0x7feac43abcd8 idx=b7 flg=0 typ=1 cur=0x7feac43abe08 lru=1 fl=15
Bucket#187 seg=0x7feac43964d8 nit=5 nal=5 ips=5 sz=56 flg=3 ucnt=1
0 cob=0x7feac43ac3f8 idx=bb flg=0 typ=1 cur=0x7feac43ac528 lru=1 fl=15
Bucket#192 seg=0x7feac43965c8 nit=5 nal=5 ips=5 sz=56 flg=3 ucnt=1
0 cob=0x7feac43043b8 idx=c0 flg=0 typ=2 cur=0x7feac43027b0 lru=1 fl=1
。。。

10g的版本trace文件:
这里会记录hdl就是session对应的library cache object handle的地址。
Session cached cursors
-----------------------------------------------------------
-------------- Generic Session Cached Cursors Dump --------
-----------------------------------------------------------
hash table=0xb7f31d00 cnt=3 LRU=0xb7f273f8 cnt=3 hit=4 max=20 NumberOfTypes=3
type#0 name=KQD count=1
type#1 name=KQD BUN count=0
type#2 name=KKS count=2
Bucket#056 seg=0xb7f323f4 nit=4 nal=4 ips=4 sz=16 flg=3 ucnt=1
0 cob=0xb7f39d80 38 flg=0 typ=0 idx=38 cur=0xb7f39b9c lru=1 flg=5 hdl=0x34cb963c
1 cob=0xb7f39d90 0 flg=0 typ=0 idx=0 cur=(nil) lru=0 flg=0 hdl=(nil)
2 cob=0xb7f39da0 0 flg=0 typ=0 idx=0 cur=(nil) lru=0 flg=0 hdl=(nil)
3 cob=0xb7f39db0 0 flg=0 typ=0 idx=0 cur=(nil) lru=0 flg=0 hdl=(nil)
Bucket#168 seg=0xb7f331f4 nit=4 nal=4 ips=4 sz=16 flg=3 ucnt=1
0 cob=0xb7f3b02c a8 flg=0 typ=2 idx=a8 cur=0xb7f4e2c0 lru=1 flg=1 hdl=0x34d8a490
1 cob=0xb7f3b03c 0 flg=0 typ=0 idx=0 cur=(nil) lru=0 flg=0 hdl=(nil)
2 cob=0xb7f3b04c 0 flg=0 typ=0 idx=0 cur=(nil) lru=0 flg=0 hdl=(nil)
3 cob=0xb7f3b05c 0 flg=0 typ=0 idx=0 cur=(nil) lru=0 flg=0 hdl=(nil)
Bucket#205 seg=0xb7f33694 nit=4 nal=4 ips=4 sz=16 flg=3 ucnt=1
0 cob=0xb7f3b0fc cd flg=0 typ=2 idx=cd cur=0xb7f3b1b8 lru=1 flg=1 hdl=0x347beec8
1 cob=0xb7f3b10c 0 flg=0 typ=0 idx=0 cur=(nil) lru=0 flg=0 hdl=(nil)
2 cob=0xb7f3b11c 0 flg=0 typ=0 idx=0 cur=(nil) lru=0 flg=0 hdl=(nil)
3 cob=0xb7f3b12c 0 flg=0 typ=0 idx=0 cur=(nil) lru=0 flg=0 hdl=(nil)

还有一个参数cursor\_space\_for\_time,该参数是为了缓解发生在child cursor上的与库缓存相关的latch争用,但是在oracle11GR1开始,mutex已经替代了各种与库缓存对象相关的latch,所以cursor\_space\_for\_time参数在oracle 11.1.0.7及其后续的版本中已经过时。

  1. oracle在解析执行sql时首先需要先获取库缓存相关的latch,然后再持有相关的cursor的library cache pin这个enqueue来pin住cursor,在sql执行完毕后oracle就会释放掉这个library cache pin,也就是相关的library cache对象会被置换出library cache,而如果有一个场景是:一个session cursor只会open和close一次,但是与之先关的child cursor中间会反复经历parse、bind、execution和fetch阶段,这样如果每次执行完毕后都需要反复的获取库缓存对象的latch和library cache pin,这样就会造成库缓存的latch争用。

注意:解析执行sql时首先获取library cache latch,在执行sql时为了防止sql对应的cursor被修改,则需要将这个sql相关的library cache对象pin到内存中,这个过程是持有library cache pin的过程,而在oracle11g后,持有library cache latch和library cache pin的过程已经被mutex所代替。

cursor\_space\_for\_time参数可以一定程度的解决库缓存的latch争用,当将此参数设置为true时,sql执行完毕后oracle也不会释放掉该sql相关的library cache pin,而是要等待游标关闭后才会释放掉相关sql的library cache pin,但是由于部分执行完毕的sql对应的child cursor并不能马上被age out出shared pool,从而导致shared pool的空间压力。

下面简单的摘要一部分官方文档中对这个参数的描述

  1. true

Shared SQL areas are kept pinned in the shared pool. As a result, shared SQL areas are not aged out of the pool as long as an open cursor references them. Because each active cursor’s SQL area is present in memory, execution is faster. However, the shared SQL areas never leave memory while they are in use. Therefore, you should set this parameter to true only when the shared pool is large enough to hold all open cursors simultaneously.

In addition, a setting of true retains the private SQL area allocated for each cursor between executions instead of discarding it after cursor execution, saving cursor allocation and initialization time.

false
Shared SQL areas can be deallocated from the library cache to make room for new SQL statements.

shared pool的内部结构远远比上面所列的要复杂,对于有些概念xiaoyu也不是理解的非常清晰,有兴趣的可以去看看相应的DSI部分。

          • -
          • -

Library cache是Shared pool的一部分,它几乎是Oracle内存结构中最复杂的一部分,主要存放shared curosr(SQL)和PLSQL对象(function,procedure,trigger)的信息,以及这些对象所依赖的table,index,view等对象的信息。

Library cache需要解决三个问题:

1.快速定位的问题:Library cache中对象众多,Oracle如何管理这些对象,以便服务进程可以迅速找到他们需要的信息。比如某个服务进程需要迅速定位某个SQL是否存在于Library cache中。

2.关系依赖的问题:Library cache中的对象存在复杂的依赖关系,当某个objec失效时,可以迅速将依赖其的对象也置为失效状态。比如某个表发生了结构变化,依赖其的SQL语句需要重新解析。

3.并发控制的问题:Library cache中必须有一个并发控制的机构,比如锁机制,来管理大量共享对象的并发访问和修改的问题,比如某个SQL在重新编译的同时,其所依赖的对象不能被修改。

Library cache结构:

Oracle利用hash table结构来解决library cache中快速定位的问题,hash table就是很多hash bucket组成的数组:

库缓存(Library Cache)内存结构教程

原理与buffer cache中定位block的方式相同,将对象信息(比如SQL)hash定位到某个hash bucket中,然后顺序扫描bucket中的 List,实现快速定位对象的目的。

Library cache handle是对象的一个指针,其中的namespace属性表示其指向的对象的类型:比如CRSR(Cursor),TABL(Table),INDX(Index) ,PROD(Procedure),TRIG(Trigger)等等。

LCO(Library cache object)是handel指向的对象,包含了以下几个部分的内容:

1.dependency table:

指向本对象所依赖的对象,比如:select * from emp这个cursor的对象,依赖emp这个表,这里指向了emp这个表的handle。

2.child table:

指向本对象的子对象,比如某个游标的子游标。子游标是指SQL文本相同,但是SQL的实际含义不同的情况,比如执行的用户不同,执行计划不同,执行的环境不同等等,我们一般称之为SQL的不同版本。一个SQL至少包含一个父游标和一个子游标。

3.authorization table:

对象的授权信息。

4.type

Library cache object的type,包括:shared cursor,index,table,cluster,view,synonym,sequence,procedure,function,package,table body,package body,trigger等等。

5.data blocks

data block是一个指针,指向了data heap,即存放真实数据的地方,主要包括:diana tree, p-code, source code, shared cursor context area等等,如下图:

库缓存(Library Cache)内存结构教程

Library cache对象依赖关系:

对象依赖关系是利用LCO中的dependency table来完成的,我们设想以下的情况,用来说明对象间的依赖关系:

两个共享游标:

SQL1: select * from emp;

SQL2: select * from emp和select a.name from emp a,dept b where a.dept\_id=b.id and b.name=:1;

库缓存(Library Cache)内存结构教程

SQL1产生了两个子游标,也就是SQL文本相同的两个不同版本,他们同时依赖emp表。SQL2只有一个版本,因为每个游标最少有一个子游标,所以它只有一个子游标,并且同时依赖dept表和emp表。

Library cache中的并发控制:

Oracle利用Library cache lock和Library cache pin来实现并发控制,Library cache lock是在handle上获取的,而Library cache pin则是在data heap上获取。访问对象时,首先必须获取handle上的lock,然后将访问的数据pin在内存中。lock的作用是控制进程间的并发访问,而pin的作用是保证数据一致性,防止数据在访问时被交换出去。

Library cache lock有三种模式:null,share,exclusive,Library cache pin有两种模式:share,exclusive。下面详细解释在修改和访问对象时,lock和pin的作用:

修改对象:

编译SQL或PLSQL对象,获取该对象(cursor,procedure)handle上exclusive类型的lock,并且持有data heap上exclusive类型的pin,防止其他人读取和修改。同时,在该对象所依赖的对象(table)上,必须持有一个share类型的lock和pin,防止在修改的过程中,被其他进程所修改。

访问对象:

访问SQL或PLSQL对象,获取该对象(cursor,procedure)handle上NULL类型的lock,并且持有data heap上share类型的pin,同时,在其依赖的对象(table)上持有share类型的lock和pin。如果一个procedure依赖另外一个function,那么在被依赖的function上,也需要持有share类型的lock和pin。

NULL类型的lock比较特殊,它只存在于cursor和procedure等只读对象上,它并不起到任何并发控制的作用,它更象是一个trigger,当对象失效时,通知所有访问这个cursor的进程。比如:select * from emp这个SQL,依赖emp表,当emp表发生变化时,cursor上的NULL lock被打破,所有有访问这个cursor的进程都会知道该对象已经失效。

当持有对象的library cache pin时,会在row cache中对相应的对象加锁,就是row cache lock,阻止可能导致数据字典信息混乱的DDL发生。

库缓存(Library Cache)内存结构教程

lock和pin的实现类似于enqueue,在每个handle上都有lock和pin的holder list和waiter list,用来保存持有该资源和等待该资源的队列。

阻塞分析:

现实情况中,我们有一个数据库中存在被应用大量频繁访问的procedure,当依赖的表发生变更时,导致该procedure失效,这时会出现大量的library cache lock和library cache pin的等待,堵塞应用访问,造成了重大故障。出现这个问题的原因是:当procedure失效后,所有访问该对象的进程都尝试去编译,大量进程尝试获取exclusive类型的lock和pin,出现了大量的等待。后续的Oracle版本作出了改进,当出现这种情况时,只允许第一个进程尝试去编译该对象,编译通过后,所有的进程就可以并发访问,避免了大量进程同时尝试编译的情况出现。

Library cache中的Latch:

Library cache中相关的latch包括:shared pool latch,library cahce latch,library cache lock latch,library cache pin latch。

Share pool latch的主要作用是分配或释放空间时使用,从Oracle9i开始,sharedpool被分成了很多个subpool,由多个shared pool latch保护,Oracle开始支持更大的shared pool。

Library cache latch的主要作用是在hash bucket中定位handle时使用,library cache lock latch和library cache pin latch分别是获取lock和pin时,需要取得的latch。

shared pool大小不合理,大量的硬解析以及SQL版本过多都可能导致shared pool latch和library cache latch的争用。

从Oracle10g开始,Oracle正在逐步用mutex取代library cache中的latch,cursor:pin S和cursor:pin X相当于share和exclusive类型的library cache pin,cursor:pin S wait on X则表示share方式正在等待exclusive锁定。

–EOF–

以下转载

一. Library Cache 说明

LibraryCache 是Oracle SGA 中Shared pool 的组成部分。

1. DSI 中对Library Cache的说明:

(1)An area in the shared pool thatmanages information about:

--Sharedcursors (SQL and PL/SQL objects)

--Databaseobjects (tables, indexes, and so on)

(2)Initially created to manage PL/SQLprograms and library units, therefore called library cache

(3)Scope was extended to includeshared cursors and information about other RDBMS objects.

2. Library Cache Objects

(1)The units of information that arestored in the library cache are called objects.

(2)There are two classes of objects:

1)Stored objects

--Created and dropped withexplicit SQL or PL/SQL commands

Examples: Tables, views, packages,functions

2)Transient objects

--Created at execution time and liveonly for the duration of the instance(or aged out)

Example: Shared and nonsharedcursors

3. Shared Cursors

(1)In-memory representation of anexecutable object:

SQLstatements

AnonymousPL/SQL block

PL/SQLstored procedures or functions

Javastored procedures

ObjectMethods

(2)Represented by two or more objects:

Aparent cursor that has a name

Oneor more child cursors containing the execution plan

  1. Library Cache Architecture

(1)The library cache is a hash tablethat is accessible through an array of hash buckets.

(2)The library cache manager (KGL)controls the access and usage of library cache objects.

(3)Memory for the library cache isallocated from the shared pool.

5. Library cache需要解决三个问题:

(1).快速定位的问题:Library cache中对象众多,Oracle如何管理这些对象,以便服务进程可以迅速找到他们需要的信息。比如某个服务进程需要迅速定位某个SQL是否存在于Librarycache中。

(2).关系依赖的问题:Library cache中的对象存在复杂的依赖关系,当某个objec失效时,可以迅速将依赖其的对象也置为失效状态。比如某个表发生了结构变化,依赖其的SQL语句需要重新解析。

(3).并发控制的问题:Library cache中必须有一个并发控制的机构,比如锁机制,来管理大量共享对象的并发访问和修改的问题,比如某个SQL在重新编译的同时,其所依赖的对象不能被修改。

Oracle利用hash table结构来解决library cache中快速定位的问题,hash table就是很多hash bucket组成的数组。 先看DSI 405 里有几张相关的图片:

库缓存(Library Cache)内存结构教程

LibraryCache 保存了explicitSQL, PL/SQLcommands,shared 和 nonshared cursors。 这些对象都保存在Hash table里,Hash table 又由Hash Bucket组成。 Hash Bucket 由一些Object Handle List 组成,所以在Hash Bucket里查找某个对象,就是搜索这个Handle List。

6. Object Handle

在上图我们可以看到Object handle 保存的信息。 Library cache handle指向library cache object(LCO, heap 0),它包含了library object的名字,命名空间,时间

戳,引用列表,lock对象以及pin对象的列表信息等等。

所以对Library cache中所有对象的访问是通过利用library cache handle来实现的,也就是说我们想要访问library cache object,我们必须先找到library cache handle。

因为Object handle保存了lock 和pin 的信息,即记录哪个用户在这个这个handle上有lock,或者是哪个用户正在等待获得这个lock。那么这里我们也知道了library cache lock是发生在handle上的。

当一个进程请求library cache object, librarycache manager就会应用一个hash 算法,从而得到一个hash 值,根据相应的hash值到相应的hash bucket中去寻找。

如果library cache object在内存中,那么这个library cache handle就会被找到。有时候,当shared pool不够大,library cache handle会保留在内存中,然而library cache heap由于内存不足被age out,这个时候我们请求的object heap就会被重载。最坏的情况下,library cache handle在内存中没有找到,这个时候就必须分配一个新的library cachehandle,同时object heap也会被加载到内存中。

7. Library Cache Object(LCO: Heap 0)

它的结构信息如下图。 这个图需要认真的去理解。

库缓存(Library Cache)内存结构教程

DSI 的说明:

(1)Internally, most of the objectidentity is represented by structures of type kglob.

(2)These are thestructures stored in heap 0.

(3)Object structures have thefollowing components:

Type

Name

Flags

Tables

Datablocks

LibraryCache 存储SQL或者shared cursors 等。 这些信息就是通过Heap 0 这个LCO 来保存的。

7.1 Object Types

(1)Objects aregrouped in namespaces according to their type.

(2)Each object can only be of onetype.

(3)All the objects of the same typeare in the same namespace.

(4)A namespace may be used by morethan one type.

(5)The most important namespace iscalled cursor (CRSR) and houses the shared SQL cursors.

7.2 Object Names

(1)Library cache object names havethree parts:

Nameof schema

Nameof object

Nameof database link (remote objects only)

(2)The format used isSCHEMA.NAME@DBLINK.

Forexample, [email protected]

7.3 Object Flags

(1)Public flags:

Arenot protected by pins or latches

Indicatein detail the type of the object

(2)Status flags:

Areprotected by pins

Indicatewhether the object is being created/dropped/altered/updated

(3)Special status flags:

Areprotected by the library cache latch

Arerelated to object validity and authorization

7.4 Object Tables

(1)Dependency table

(2)Child table

(3)Translation table

(4)Authorization table

(5)Access table

(6)Read-only dependency table

(7)Schema name table

Object Table 又分为以上7中类型。

7.4.1 dependency table

指向本对象所依赖的对象,比如:select * from emp这个cursor的对象,依赖emp这个表,这里指向了emp这个表的handle。

7.4.2.child table

指向本对象的子对象,比如某个游标的子游标。通俗点说,就是一条SQL 至少有一个parent cursor 和 child cursor。 可能一些SQL 由于某些原因无法共享childcursor,这样就会出现一个parentcursor 和 多个child cursor的情况。 即version count 很高。 那么这种情况下。 parent cursor 里对应的所有childcursor的指针都会保存在child table里面。 Oracle 是用C 写的,所以这里是指针。

注意一点,parent cursor和child cursor都是用library cache object handle 存储在Library Cache里的。即他们的结构完全一样。

这个结论可以通过library cache的dump 文件来证明。在后面我们会做一个测试。

7.4.3.authorization table

对象的授权信息。

7.5 Object Data Blocks

(1)The remainder ofan object’s data is stored in other independent data heaps.

(2)The object structure contains anarray of data block structures.

(3)The data blockstructures have a pointer to a different data heap.

(4)An object structure has room for 16data block structures but not all of them are in use.

Heap0 也仅仅保存是一个结构,它不保存实际的data。 而实际data 的存储Heap 的指针就存放在这个Data Blocks里。 这个也可以通过dump 来查看。这个Data Blocks指向的Heap 结构如下图:

库缓存(Library Cache)内存结构教程

这里要注意的,就是我们SQL的执行计划就是存放在这个Heap 6:SQL Context 中。

二. 测试

在上面的说明中,提到,对于多child cursor,所有的child cursor 都保存在child table里,并且SQL 的执行计划也保存在child cursor的Heap 6中。 下面进行相关的测试,证明以上结论。

SYS@anqing1(rac1)> create table lct asselect * from dba\_objects;

Table created.

分别用SYSTEM和Dave 用户执行如下SQL:

DECLARE

x VARCHAR2 (20);

str VARCHAR2 (200);

BEGIN

x := 'I\_PROXY\_ROLE\_DATA$\_1';

str := 'select* from sys.lct where object\_name=:x';

EXECUTE IMMEDIATE str USING x;

END;

/

查看SQL 的version count信息

SYS@anqing1(rac1)> selectsql\_id,version\_count from v$sqlarea where sql\_text like 'select * from sys.lctwhere %';

SQL\_ID VERSION\_COUNT

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

5d8tu19t1fug2 2

--查看parentcursor 和 child cursor的handle address:

SYS@anqing1(rac1)> selectaddress,child\_address from v$sql where sql\_id='5d8tu19t1fug2';

ADDRESS CHILD\_AD

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

30A9DCDC 2CBA64AC

30A9DCDC 2A32C000

将library cache dump出来:

SQL> alter session set events 'immediatetrace name library\_cache level 11';

Session altered.

SQL> oradebug setmypid

Statement processed.

SQL> oradebug tracefile\_name

/oradata/XEZF/admin/udump/xezf\_ora\_14041.trc

--查看trace 文件:搜索30A9DCDC

BUCKET 92642:

LIBRARY OBJECT HANDLE:handle=30a9dcdc mtx=0x30a9dd90(2) cdp=2

--object handle 的内存地址

name=select * from sys.lct whereobject\_name=:x

--object 的名称

hash=55555e74e494aa0356a33a0a721769e2 timestamp=07-24-2011 15:03:04

--hash值和timestamp

namespace=CRSRflags=RON/KGHP/TIM/PN0/SML/KST/DBN/MTX/[120100d0]

--namespace 和 flags

kkkk-dddd-llll=0000-0001-0001 lock=N pin=0latch#=1 hpc=0006 hlc=0006

--Heaps loaded and kept,lock, pin, and latch modes

lwt=0x30a9dd38[0x30a9dd38,0x30a9dd38]ltm=0x30a9dd40[0x30a9dd40,0x30a9dd40]

pwt=0x30a9dd1c[0x30a9dd1c,0x30a9dd1c]ptm=0x30a9dd24[0x30a9dd24,0x30a9dd24]

ref=0x30a9dd58[0x30a9dd58,0x30a9dd58]lnd=0x30a9dd64[0x30a9dd64,0x30a9dd64]

--以上是:Linklists of lock waiters, temporary locks, pin waiters, temporary pins andreferences

LOCK OWNERS:

lock user session count mode flags

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

2d1a0cac 30efbd20 30efbd20 2N [00]

2d10501c 30f10e78 30f10e78 2N [00]

LIBRARY OBJECT: object=2aa6cf2c

--Memory address of theobject (heap 0)

type=CRSR flags=EXS[0001] pflags=[0000] status=VALD load=0

--Object type, flags, andstatus

CHILDREN: size=16

child# table reference handle

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

02c08c08c 2c08bd5c 2cba64ac

1 2c08c08c 2c08bec8 2a32c000

--这个就是所有child cursor 的handle 的内存地址

DATA BLOCKS:

data# heap pointer status pins change whr

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

0 2b8839b4 2aa6cfc4 I/P/A/-/- 0NONE 00

--Object data structures(heap descriptors)

BUCKET 92642 total object count=1

查看其中一个child cursor 地址,在trace 里搜索:2cba64ac

LIBRARY OBJECT HANDLE: handle=2cba64acmtx=0x2cba6560(0) cdp=0

namespace=CRSR flags=RON/KGHP/PN0/EXP/[10010100]

kkkk-dddd-llll=0000-0001-0001 lock=N pin=0 latch#=1 hpc=0002 hlc=0002

lwt=0x2cba6508[0x2cba6508,0x2cba6508] ltm=0x2cba6510[0x2cba6510,0x2cba6510]

pwt=0x2cba64ec[0x2cba64ec,0x2cba64ec]ptm=0x2cba64f4[0x2cba64f4,0x2cba64f4]

ref=0x2cba6528[0x2c08bd5c,0x2c08bd5c]lnd=0x2cba6534[0x2cba6534,0x2cba6534]

CHILD REFERENCES:

reference latch flags

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

2c08bd5c 0 CHL[02]

LOCK OWNERS:

lock user session count mode flags

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

2d345160 30efbd20 30efbd20 1N [00]

2d1a0bdc 30f10e78 30f10e78 1N [00]

LIBRARY OBJECT: object=2c1a3280

type=CRSR flags=EXS/RIV[0201] pflags=[0000] status=VALD load=0

DEPENDENCIES: count=1 size=16

dependency# tablereference handle position flags

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

0 2db7d838 2db7d770 2bb25644 18 DEP[01]

AUTHORIZATIONS: count=1 size=16 minimum entrysize=16

00000000 30000000 00020000 00000000

ACCESSES: count=1 size=16

dependency# types

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

0 0009

SCHEMA: count=1 size=262144

0000003d

DATA BLOCKS:

data# heap pointer status pins change whr

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

0 2a078574 2c1a3318 I/P/A/-/- 0NONE 00

6 2aa6d2542c4f9cf0 I/-/A/-/E 0 NONE 00

这个结构和Heap 0的差不多。 但是这个child cursor 没有name了。 因为我们访问时先通过parent cursor。 在到child cursor。所以parent cursor 有name 就可以了。

这里的Data blocks 有2个: data block 0和data block 6. 对应heap 0 和heap 6. 关于Heap 0的说明如下:

The data block structure for a heap, stored in heap 0,contains a pointer to the first data block that is allocated for the heap, astatus indicator, the pin under which the heap is loaded, and so on.

执行计划保存在Heap 6里,我们把Heap 6 dump 出来,看一下:

SYS@anqing1(rac1)> alter session setevents 'immediate trace name heapdump\_addr level 2,addr 0x2aa6d254';

Session altered.

--注意,这里的addr前面加了0x。

SYS@anqing1(rac1)> oradebug setmypid

Statement processed.

SYS@anqing1(rac1)> oradebugtracefile\_name

/u01/app/oracle/admin/anqing/udump/anqing1\_ora\_1533.trc

在trace 里搜索:0x2aa6d254

HEAP DUMP heapname="sql area" desc=0x2aa6d254

extent sz=0xff4 alt=32767 het=156 rec=0 flg=2opc=2

parent=0x2000002c owner=0x2aa6d1c8 nex=(nil)xsz=0xff4

EXTENT 0 addr=0x2a73d5e8

Chunk 2a73d5f0 sz= 788 free " "

Dump of memory from 0x2A73D5F0 to0x2A73D904

2A73D5F0 C0000315 00000000 2AA6D2B42AA6D2B4 [...........*...*]

2A73D600 2B10E1FC C0000051 000000002C49E55C [...+Q.......\.I,]

2A73D610 2AEA8820 00314E00 0000800400010035 [ ..*.N1.....5...]

2A73D620 00930024 00020001 0019003A00020074 [$.......:...t...]

2A73D630 001A0019 000200BF 007400B000500004 [..........t...P.]

2A73D640 001A0024 00BF0033 0003001200010022 [$...3......."...]

2A73D650 62000006 0000024D 2A73D6040C9D040C [...bM.....s*....]

2A73D660 00000000 2A73D8A0 0035000000240001 [......s*..5...$.]

2A73D670 00010093 003A0002 0074001900190002 [......:...t.....]

2A73D680 00BF001B 00B00002 0004007400240039 [........t...9.$.]

2A73D690 0033001B 001200BF 0022000300060001 [..3.......".....]

2A73D6A0 12021400 00010022 0A000006006B0802 [....".........k.]

2A73D6B0 03671F85 EF042C04 001C004E000C0000 [..g..,..N.......]

2A73D6C0 001D0400 001C0056 000002E0001D0040 [....V.......@...]

2A73D6D0 00B10028 000D0028 008C00260026005E [(...(...&...^.&.]

2A73D6E0 00A40026 000E000C 0E00002612011201 [&.......&.......]

2A73D6F0 00010022 AC000009 00AF0A0000010021 ["...........!...]

2A73D700 00020074 00210009 001500BF00780026 [t.....!.....&.x.]

....

dbsnake blog里的总结如下:

heap6实际上就是sql area(这个和上图中描述的heap 6是sql context不尽相同)。 Oracle把sql的执行计划存在了这个sql的子cursor的heap 6(也就是sql area)中,只不过存储的形式是编译好的二进制格式。

MOS 上的相关说明:

Parsinga cursor builds four different library cache structures, if they do not alreadyexist, within the library cache:

1、parent cursor handle

2、parent cursor object, containing the child dependency list

3、child cursor handle, inserted in the child dependency list of theparent object

4、childcursor object, containing the compilation and run-time execution plan for thecompiled SQL statement.

标签: 缓存, cache, 游标, Cache, SQL, sql, library, cursor, Library

相关文章推荐

添加新评论,含*的栏目为必填