Library cache位于Oracle实例SGA中的shared pool,用于缓存SQL游标、PLSQL程序以及Java类的可执行形式。
当一条SQL运行时,如果它在不久前已经被执行过,那么Oracle会尝试重用这条SQL的。一条从未执行过的SQL进入数据库获取数据需要经过编译和解析,然后生成最终的可以直接调用的执行计划结果,再按照这个执行计划来从相关的数据库表中获取数据。如果语句解析后的结果已经缓存在了library cache中,那它就能够被重用,这就叫做软解析,或者叫library cache hit。相反,如果library cache中没有缓存,则必须重新解析生成新的执行计划,然后放入library cache中执行,这就叫做硬解析,或者叫library cache miss。
当执行SQL时,library cache miss可以发生在解析阶段或执行阶段。当解析调用SQL语句时,如果语句的解析表达式不存在于library cache中,数据库会解析语句然后将解析后的结果存放在shared pool中。
- 硬解析:
提交一条SQL语句,并且shared pool中是找不到匹配的项。硬解析是资源集中型操作,因此扩展性不强,因为一个语句解析操作会包含大量复杂的工作。 - 软解析:
提交一条SQL语句,并且shared pool中能够找到匹配的项。这个匹配的项可以是另一个用户之前执行后生成的。SQL语句被共享,这能够提升性能。但是,软解析也并不完美,因为他们仍然需要语法和安全性检查(受DDL语句影响),仍然要耗费系统资源。
硬解析应该尽可能少,所以应用开发人员在设计系统时应该考虑如何让SQL语句在只解析一次的情况下执行更多次。这需要用到游标(cursors)。有经验的SQL程序员应该熟练掌握打开和重执行游标的概念。
必须确保SQL语句在共享池中被共享。使用绑定变量来表式多个相似SQL语句中不一样的部分。否则,有可能出现特别多的语句在第一次解析之后就再也不会被重用。
例如:
SELECT * FROM employees
WHERE last_name LIKE 'KING';
应该改成
SELECT * FROM employees
WHERE last_name LIKE :1;
library cache结构和概念
所有SQL和PLSQL语句的执行计划都存放在Library cache内存区
而library cache可以进一步划分为:
- 共享SQL区和私有SQL区
- PLSQL存储过程部分
共享的和私有的SQL区域
共享的SQL区包含一条单独SQL语句或多条相似SQL的解析树和执行计划。Oracle让多条相似的DML语句使用同一个共享SQL区,从而达到节省内存的目的。尤其是当多个用户使用同一个应用时。
共享SQL区总是在shared pool中。解析一条SQL语句时会从shared pool中请求内存。如果一条SQL语句需要新的SQL区,但shared pool中没有剩余空间时,Oracle会根据LRU算法来清理pool中的内容,直到空间足够分配新的共享SQL区。
私有SQL区包含类似于绑定信息和运行时缓存这类数据。每个执行SQL的会话都有一个私有SQL区。每个提交SQL语句的会话用户都有自己的私有SQL区,这个私有SQL区只会使用一个单独的共享SQL区。而同一个共享SQL区可以和多个私有SQL区关联使用。
私有SQL区里有一个固有区和一个运行时区:
- 固有区包含多次执行且重复使用的绑定信息,数据类型转换代码(当数据类型定义和所选列数据类型不一致时),以及其他状态信息(比如,循环或远程游标数量或者并行查询的状态)。固有区的大小取决于绑定变量和语句查询列的个数,比如,如果一条查询中出现了许多列,则固有区会更大
- 运行时区包含SQL语句被执行时需要使用的信息。大小取决于SQL语句的类型和复杂程度以及语句处理的行的大小。通常,INSERT、UPDATE、DELETE的运行时区比SELECT语句的运行时区更小,尤其是对于需要排序的SELETE语句。
oracle在收到执行请求的第一时间就开始创建运行时区。对于INSERT、UPDATE、DELETE语句,oracle会在执行完成后释放掉运行时区。对于SELECT查询,Oracle会等到所有行被提取后或者查询被取消后才会释放运行时区内存。
私有SQL区的位置取决于会话建立连接的类型。如果是专有服务器连接模式,私有SQL区位于用户的PGA。如果是共享服务器连接模式,则固有区、和SELECT查询的运行时区
都会存放在SGA。
x$ksmss表中提供了关于library cache中SQL区的运行时信息
PLSQL过程部分
oracle处理PLSQL程序和处理单独SQL语句的方式大致相同。
oracle分配共享区来存放程序单元的解析、编译形式,分配私有区来存放每个会话在执行程序单元时特有的值,其中包括本地变量、全局变量、和package变量以及执行SQL时需要的缓存。如果多个会话执行同一个程序单元,所有会话共享一个共享区,而各个会话维护各自的私有SQL区,持有各自会话特有的值。
PLSQL程序单元中的单独SQL语句和PLSQL程序外的SQL语句一样,尽管处于PLSQL程序单元中,这些SQL语句仍然使用共享区来持有解析形式,使用私有区来存放会话特有的信息。
x$ksmss表中提供了关于library cache中PLSQL区的运行时信息
Library Cache管理器
library cache的主要目的是提供一种快速定位和存储library cache object(LCO)的机制。使用一种hash机制来定位含有object id的handle。library cache handle(LCH)指向一个或多个library cache objects以及它们的内容
library cache缓存了不同类型的library cache object(比如,packages,procedures,functions,shared cursors,命名PLSQL块,表定义,视图定义,form定义)
KGH heap Manager:
shared pool和PGA都是由一个Oracle的内存管理器来管理,我们称之为KGH heap manager。它不是一个进程,而是一串代码。heap Manager主要目的是满足server进程请求内存的时候分配内存或者释放内存。Heap Manager在管理PGA的时候,需要和操作系统打交道来分配或者回收内存。但是,在shared pool中,内存是预先分配的,Heap Manager管理所有的空闲内存。
当某个进程需要分配share pool的内存的时候,Heap Manager就满足该请求,heap manager也和其他Oracle模块一起工作来回收shared pool的空闲内存。
library cache内存是从SGA heap的最顶端开始分配内存。当Library cache需要更多内存的时候,会调用KGH heap manager来分配。Library cache是由一个hash表组成,hash表是一个由许多hash buckets组成的向量。每个hash bucket是由多个LCH组成的双链列表。每个handle指向一个object并且有一个reference list。
Library Cache Manager(hash表和hash bucket)
Library cache manager(LCM)可以看做是Heap Manager的客户端,因为LCM是根据Heap Manager来分配内存从而存放LCO。LCM控制着所有的LCO,包括package/procedure,cursor,trigger等等。
library cache是有hash table组成,这个hash table又由hash bucket组成的数组构成,每个hash bucket又是由一些相互指向的LCH组成,LCH指向具体的LCO以及一些引用列表。
hash table初始化由251个hash buckets组成,当hash table中的buckets不够用时,会自动扩展长度,每次扩展之后,buckets数量大约是原来的2倍,但这个值必定是一个质数。准确地讲,如果是第n次扩展,则扩展后的长度为不大于2^(n+8)的最大质数。hash table在扩展的时候,实际上是新建了一个长度大约为原来2倍的新的hash table,然后将原来hash table上的对象重新组织在新的hash table上,因此,hash table在扩展的时候是不可访问的。但这是一个不怎么耗资源的操作,一般来说3-5秒内即可完成。
hash table只会扩展不会收缩。
Library Cache Handle
Library Cache Handle(LCH)执行Library cache object(LCO).它里面含有LCO的name、namespace、timestamp、reference list、lock请求列表和pin请求列表。每个LCO都是通过LCH里的name和namespace联合起来作为唯一标识。
共享池:library cache统计
在规划共享池大小时,我们的目的是确保多次执行的SQL语句被缓存在library cache中,不需要分配太多内存。
v$librarycache视图是用于统计自最近一次数据库重启以来的library cache中各个组件的使用情况。
v$librarycache视图中的reload列表示被踢出缓存的SQL语句被再次执行硬解析的次数。这个值应该尽可能少或接近于0.
v$librarycache视图中的invalidations列表示library cache中的数据失效,从而需要重新解析的次数,通常,DDL语句改变了元数据使得SQL语句解析缓存失效时,这个值会增加。
另一个关键指标是共享池在业务高峰期的剩余内存。可以通过v$sgastat视图查看。剩余内存应该尽可能少
SQL解析执行全过程总结:
当一条SQL进来:
如果这条SQL为全新文本
文本经过hash算法生成哈希值,到PGA里去匹配,除非出现hash碰撞(概率极小),否则不可能找到,所以需要到SGA里的shared pool里的library cache里找,因为是全新文本,所以library cache中也肯定不会有现成的(除非hash碰撞),因此需要进行硬解析,也就是,在library cache中的hash buckets链中新增一个bucket,这个bucket里会存放一个parent cursor和一个child cursor,parent cursor和child cursor具有相同的结构,统称为shared cursor,是library cache中存放的几十种library cache object中的一种。parent cursor和child cursor虽然具有相同的结构,但他们的功能不一样parent cursor数据结构里主要存放的是SQL文本,存放在name属性里,不存放SQL的解析树和执行计划,而child cursor的name属性为空,主要用于存放SQL的解析树和执行计划。parent cursor和child cursor从功能上讲是从属关系,一个parent cursor可以挂一个或多个child cursor,新建的bucket下会初始化生成一个parent cursor和一个child cursor。新增bucket的过程中会持有library cache latch,维护新增操作的原子性,同时需要为新增的bucket分配内存,所以还要持有shared pool latch。当新的bucket生成并成功返回给PGA后,shared pool和library cache
latch才能得以释放,然后才会在PGA里对cursor进行open、parse、bind、execute、fetch、closed(或soft closed)等操作。如果这条SQL曾经执行过,至少SQL文本相同
文本经过hash算法生成哈希值,PGA中有可能有现成的,也有可能没有,取决于session_cached_cursors参数,这个参数控制着PGA里是否缓存执行过的cursors,缓存多少个。如果PGA里有现成的,说明当前会话在不久前执行过这条SQL,并且cursor在open并使用之后被标记为了closed,并未真正意义上的被清理出PGA,这就叫soft closed,PGA可以直接对这个已经缓存的session cursor进行操作,不需要open一个新的session cursor。 这样的SQL解析叫做软软解析,是消耗资源最少的一种解析方式。如果一条SQL文本在数据库里执行过,但PGA里没有现成的,原因有可能是:
(1) 其他会话执行过,当前会话没执行过
(2) 当前会话执行过,但没有缓存(session_cached_cursors参数控制)
如果是原因(1),该SQL文本在其他会话执行过,PGA会根据SQL文本对应的hash值到library cache中的hash buckets链中搜索,找到对应的bucket以及parent cursor,然后顺着parent cursor一一验证挂在其下的child cursor,child cursor有可能有多个,因为虽然SQL文本相同,SQL执行环境未必相同,比如SQL执行的用户不同则语句涉及的表或试图等对象也可能不同,会话参数不同则产生的解析树和执行计划也有可能不同,因为是其他会话执行的,所以执行环境也不尽相同。如果经过验证能够在bucket中找到可以复用的child cursor,则将该child cursor返回给PGA,这中情况属于软解析。如果没有可以复用的child cursor,则会在bucket中新增一个child cursor,然后返回给PGA作为session cursor进行后续操作。新增child cursor仍然需要从shared pool中分配内存,持有shared pool latch和library cache latch(或mutex),所以这也属于硬解析。区分硬解析于软解析的关键点在于是否新增child cursor。
如果是原因(2),在同一个会话中,第二次执行相同的SQL文本,由于PGA中的缓存没有了,于是到Library cache的hash buckets链中按照SQL文本的hash值搜索,根据parent cursor找到SQL文本相同的bucket,然后搜索挂在这个bucket下的child cursor,如果能够找到可以复用child cursor,然后就返回给PGA。虽然是第二次执行,仍然有可能找不到可以复用的child cursor。实际上,child cursor是挂在hash buckets链的一个支链上的二进制可执行文件,属于library cache object,每个library cache object都有一个handle,这个handle上有个叫heap 0的数据结构,heap 0中存放了能够验证child cursor是否能够复用的验证信息,比如,SQL语句涉及哪些对象(准确地说,是哪些library cache object),heap 0中还存放了执行child cursor二进制可执行文件(data heap中)的地址指针,以便能够顺着handle找到child cursor。handle中仅包含验证信息和指针等较小的数据结构,因此占用空间很少,而child cursor才是存放解析树和执行计划等空间占用较多的数据结构。因此,当shared pool空间不足时,会根据LRU算法将挂在handle下的child cursor二进制可执行文件清理出内存,但handle仍然保留,当handle再次被成功验证可以复用,却找不到二进制可执行文件时,需要重新生成执行计划和解析树,然后在支链上新增一个handle,并将child cursor挂在新增的handle上,然后才将原来的空handle拿掉,这种情况叫做reload,仍然属于硬解析。当然如果是原因(1),同样有可能出现这种情况。
library cache pin和library cache lock
- 简介
library cache lock和library cache pin都是用于管理不同进程对library cache中的对象的并发访问的。但是library cache pin用于管理cache 的一致性。要想访问library cache 中的对象,必须先lock住library cache object handle,然后pin住对象的data heap本身。pin和lock请求在被许可之前都需要一直等待。这就有可能引起资源的竞争,因为两种请求都不存在NOWAIT请求模式。
当一个进程获取了某个object handle的library cache lock的时候,其他进程便不能访问这个对象,甚至连这个object是什么类型的都无法得知。lock还可以维护一个object的依赖关系,但不会阻止其他进程访问依赖的对象。
如果进程想要真正使用或修改这个object,那么必须获得object data heap 本身的library cache pin,也就是在获得lock之后获得。pin住object的过程中,如果data heap的信息和数据被aged out,则必须将它们重新加载进来。data heap被pin住的时候,不能被aged out出内存。lock和pin的信息可以分别通过x$kgllk
和x$kglpn
两个视图查看
library cache lock模式
- library cache lock有三种模式,分别为share、exclusive、null。
如果只是读取object,则获取(S)share模式的锁;如果需要修改object,则获取(X)exclusive模式的锁;Null锁比较特殊,用于维护object的依赖对象,在访问object的时候会一直持有,持有期间,object的依赖对象可以被其他进程访问甚至修改,通常,如果依赖对象被修改,则Null锁被打破,object会被标记为invalid。object下次被访问时,则需要重新解析成二进制可执行文件。
- library cache pin模式
library cache pin有两种模式,分别是share和exclusive。一般来说,share模式lock用于只读访问,exclusive模式lock用于修改。但无论只读访问还是修改,总是要先获取object 的share lock,进行错误检查和安全性检查,然后,如果需要修改object,再追加exclusive lock。如果一个object只需要只读访问,就绝不会被exclusive pin。这是因为当object