library cache最主要的功能就是存放用户提交的SQL语句、SQL语句相关的解析树(解析树也就是对SQL语句中所涉及的所有对象的展现 )、 执行计划、用户提交的PL/SQL程序块(包括匿名程序块、存储过程、包、函数等)以及它们转换后能够被Oracle执行的代码等。为了 对这些 内存结构进行管理,library cache中还存放了很多控制结构,包括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程序块到shared pool以后,会在library cache中生成一个可执行的对象,这个对象就叫做游标(cursor )。 不要把这里的游标与标准SQL(ANSI SQL)的游标混淆起来了,标准SQL里的游标是指返回多条记录的SQL形式,需要定义、打开、关闭 。下 面所说到的游标如无特别说明,都是指library cache中的可执行的对象。游标是可以被所有进程共享的,也就是说如果100个进程都 执行相 同的SQL语句,那么这100个进程都可以共用该SQL语句所产生的游标,从而节省了内存。

 

每个游标都是由library cache中的两个或多个对象所体现的,至少两个对象。一个对象叫做父游标(parent cursor),包含游标的名 称以 及其他独立于提交用户的信息。从v$sqlarea视图里看到的都是有关父游标的信息;另外一个或多个对象叫做子游标(child cursor) ,如 果SQL文本相同,但是可能提交SQL语句的用户不同,或者用户提交的SQL语句所涉及的对象为同名词等,都有可能生成不同的子游标。 因为 这些SQL语句的文本虽然完全一样,但是上下文环境却不一样,因此这样的SQL语句不是一个可执行的对象,必须细化为多个子游标后 才能够 执行。子游标含有执行计划或者PL/SQL对象的程序代码块等。

 

在介绍library cache的内部管理机制前,先简单介绍一下所谓的hash算法。

 

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

 

我们来列举一个最简单的hash算法。假设我们的数值列表最多可以有10个元素,也就是有10个hash bucket,每个bucket最多可以包含 10个 数值。则对应的二维数组就是t[10][10]。我们可以定义hash算法为n MOD 10。通过这种算法,可以将所有进入的数据均匀放在10个 hash  bucket里面,hash bucket编号从0到9。比如,我们把1到100都通过这个hash函数均匀放到这10个hash bucket里,当查找32在哪里时 ,只要 将32 MOD 10等于2,这样就知道32必定位于2号hash bucket里,于是到t[2][10]里去找,2号hash bucket里有10个数值,逐个比较2 号hash  bucket里是否存在32就可以了。这里可以看出,为了找到32这个值,我们总共需要比较11次(1+10)。如果不使用hash算法,而采 用遍历扫 描的方式,则需要比较100次。

 

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

 

当一条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里去。

 

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

 

对象句柄存放了对象的名称(name)、对象所属的命名空间(namespace)、有关对象的一些标记(比如对象是否为只读、为本地对象 还是 远程对象、是否被pin在内存中等)以及有关对象的一些统计信息等。其中存放的最重要的内容应该就是指向Heap 0对象的指针了。 Heap 0 用来存放与对象有直接关系的一些信息,比如对象类型、对象相关的表、实际的执行计划、执行PL/SQL的机器码等。Heap是由一个 或多个 chunk组成的,这些chunk可以是分散的分布在library cache中的,不需要连续分布。

 

我们可以通过查询视图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;

 

而dictionary cache则是专门用来存放SYS schema所拥有的对象的内存区域。使用dictionary cache时以行为单位,而不像其他比如 buffer  cache以数据块为单位,因此dictionary cache也叫做row cache。构造dictionary cache的目的是为了加快解析SQL语句的速度, 因为 dictionary cache里存放了所有表的定义、Storage信息、用户权限信息、约束定义、回滚段信息、表的统计信息等。基本上我们不需 要过 多地关注它。

 

Oracle里是没有初始化参数来控制library cache和dictionary cache应该占多大的内存的,我们只能控制shared pool的大小。因为前 面说 过,shared pool里的一个可用chunk,如果存放了数据字典的信息,那么它就属于dictionary cache。否则,如果存放了SQL文本或执 行计 划等信息,则它就属于library cache。有时某chunk原先可能放的是SQL文本,后来由于内存不足被数据字典信息所覆盖,则该chunk 就从 library cache变成了dictionary cache。所以我们不能单独控制library cache和dictionary cache各是多大。