本文中,我们将讨论共享池(Shared Pool)中的各种内存保护结构,即Latches,Locks,Pins和Mutexes。
1. Lathes
当在库缓冲(Libraray Cache)中创建新对象而没有足够的自由内存(没有足够大的单个自由内存块(Chunk of Free Memory))时,Oracle就会用LRU算法从相应哈希链表上分离(Delink)已有对象,并将新对象加入哈希链表上,该过程中,都会涉及各个对象中前后地址指针的修改,也会涉及各对象在自由链表及库缓冲链表间的移动。
如上场景中,Latches将派上用场。Latches是保护系统全局区中共享数据结构的简单、低级的串行机制。Latches消除了多进程同时修改共享内存时面临的冲突问题。当服务器或后台进程操作或访问这些共享数据结构时,将会在很短时间内获取Latches。
共享池Latches用于保护和获取共享池的并发访问。一个Latch可以覆盖多个哈希桶(Hash Buckets),因此,一个进程在哈希桶中搜索某个对象前,必须先获取保护该哈希桶的Latch,然后,搜索整个哈希桶链表,完成需要的所有处理和操作后,再释放该哈希桶上的Latch。如果此时另一个用户也想搜索同一个Latch下的某个哈希桶链表,那么,就必须等待前面的用户释放覆盖该哈希桶链表的那个Latch,这就导致了Latch冲突。
2. Latches冲突——Locks和Pins
当Latch被长久持有或Latch需求太高时,就会产生Latch冲突。影响Latch
冲突的因素主要有三个。
1)覆盖整个库缓冲的Latches数。如果Latches数较多,那么,每个Latch保护的哈希桶数就会更少,当你需要搜索某个哈希桶链表时与其他使用相同Latch的用户发生冲突的几率就会少;另一方面,如果系统中有更多的Latches,那么,系统将会有更多的维护、报告或清理任务要做。
共享池架构中,有固定数目的库缓冲Latches保护固定数目的哈希桶,当然,也会按需增长。
直到Oracle 10g版本,覆盖库缓冲的Latches数都非常少。该数目依赖于系统上的cpus数,应该是与cpu_count参数值相当在,直至最大达到67个Latches。这个数目出奇的小,以至于,即使在系统上运行少量而频繁执行的不通过语句,即使存取不同的哈希桶,但可能因为获取相同Latch而发生Latches冲突。
2)需要获取特定Latches的次数。用户获取特定Latch并遍历相应链表的次数越多,与其他获取同样Latches的其他用户产生冲突的可能性就越大。
一旦发现某个对象,我们可以给该对象附加一个KGL Locks(kgllock),从而使得对库缓冲的搜索次数最小化,这样,我们就拥有一个到该对象的捷径(当打开/关闭游标时存储于PGA中)。当下次同样的语句发布时,我们就可以在PGA中找到该对象而不必再搜索相应的哈希桶链表(软软解析)。
3)必须持有Latch的时长。大家持有Latch的时间越长,那么,与其他持有该Latch的其他人产生冲突的可能性就越大。
我们应该避免长时间持有Latches。这就意味着当我们对已发现的某个内存区域做一些耗时处理时,可以将该对象Pin住,以保护我们正使用的内存区域,从而我们能释放相关Latch。
因此,服务进程发现相应哈希桶后,随后的步骤如下所示。
a)获取覆盖相应哈希桶的Latch;
b)获取特定对象上的Lock以便将指向该对象的指针放入PGA中(当打开游标时);
c)将该对象Pin住并释放相应Latch;
d)完成特定对象上的所有处理和操作(例如:执行语句/过程);
e)获相应Latch,Unpin该对象并释放相应Latch;
除非发生修改,否则,Locks和Pins通常处于共享模式。
3. Lirary Cache Locks(KGL Locks) 如何被获取?
可以通过三种主要方式可以获取KGL Locks:
2)用户可以设置session_cached_cursors参数,以使得Oracle看到用户使用一条语句多于两次时,库缓冲将自动持有相关游标;
3)用户将得益于PL/sql调用中打开(显式或隐式)的游标将被持有的半自动方式,该功能在Oracle 9.2.0.5以上版本中被支持。该特性也将通过session_cached_cursors参数设置进行控制。当代码不显式持有游标时,该参数设置将被持有的游标数,该参数也控制持有会话运行PL/sql代码打开的游标数。
4. KGL Locks的好处
除了最小化我们搜索对象时对库缓冲的检索次数外,该Locks还提供如下好处:
1)通过Locks方式,一个用户可以阻止其他用户存取相同的对象。库缓冲Locks用对象句柄作为资源结构,且获取该资源上的Locks。如果资源处于不兼容模式而不可用,则会话必须等待库缓冲对象变为可用。
2)也被称为解析(Parse)Locks的库缓冲Locks用于维护对象及其依赖对象的依赖机制。
5. 为什么需要库缓冲Pins(KGL Pins)?
当一个对象被实际使用时,KGL Pin才会发挥作用。虽然KGL Locks将持有内存中的对象(PGA中的游标指向共享池中的对象),但如果系统上内存压力较大,对象的某些动态创建部分(例如:sql语句的执行计划)也将会被移出内存。然而,当一个对象被实际使用时,必须确保其可重建部分不能被移出内存,因此,为了确保这点,需要将这些对象Pin住。
Pin住对象会导致其相关堆被加载进内存。如果一个用户想修改或检查该对象,则必须获取Lock后再获取Pin。
如果一个用户想修改库缓冲对象依赖的底层对象,则需要首先以独占模式获取该底层对象上的库缓冲Pin。如果依赖库缓冲对象(例如:sql语句)正在执行,则这些Pins将因为不可用而发生等待。因此,库缓冲对象上的解析Locks被破坏前,独占模式的库缓冲Pins必须首先被获取,以便对库缓冲对象进行更改。
库缓冲对象上Locks及Pins的相关信息可通过三个X$表进行查询,即x$kgllk,x$kglpn和x$kglob.
1)x$kgllk包含一个对象上的所有Locks结构;
2)x$kglob包含Locks资源项;
3)x$kglpn 包含所有的库缓冲Pins。
6. KGL Locks及Pins相关问题
库缓冲锁及钉住(Locking和Pinning)机制也许会导致两个问题。
KGL Locks及Pins本身只是小内存区域,根据需求同时被分别创建和废弃,并从共享池分配内存。因为KGL Locks大约占用200个字节,而KGL Pin大约占用40个字节,因为相关它们的内存经常分配和释放,导致共享池出现蜂窝状的自由内存,即自有内存总数很多,但没有连续的大内存块。因为库缓冲Pins来去很快而非常讨厌,KGL Locks则因为能和对象存在一段时间而不是那么太糟糕。
KGL Locks及Pins相关的另一个问题是,当使用它们时,必须频繁的创建内存区域,标明属性,将它们插入链表(或移出链表并将其放回共享池),期间,这些操作需要一直持有独占模式的Latch,对繁忙的系统来说,整个库缓冲Locks及Pins将带来很大威胁。
Oracle10g版本中,Oracle公司引进了库缓冲Lock Latch和库缓冲Pin Latch,这将允许对相同Latch覆盖的不同哈希桶进行某些并发操作。然而,当我们升级到Oracle 11版本时,整个KGL Lock及Pin机制逐渐被Mutex机制替代。
7. Mutexes
为了改善游标的执行和硬解析,Oracle 10gR2版本引入了一个新的、更好粒度的内存串行机制。对某些共享游标相关的操作,Mutexes(Mutual exclusion objects,互斥对象)可用来替代库缓冲Latches和库缓冲Pins。Mutex工作方式与Latch基本相同,但操作Mutexes的代码路径更短,更轻量级,通常也会被硬件直接支持。因此,与Latch机制相比,Mutexes会更快,耗费更少的cpu,并发性方面也会有很大提升。32位Linux系统上,常规Latch结构占用110字节,而Mutex仅占28字节。而Mutex消耗的指令也更少。获取一个Latch需要消耗150~200个指令,而获取一个Mutex则只需大约30-35个指令。
因为Mutex提供较少关于谁正等待什么及等待时长等信息,所以,Mutex结构较小。你可以获取Mutex睡眠次数的信息,但不能获取需求次数及命中失败次数。
8. Mutex的好处
Mutexes可以共享或独占模式获取,也可以等待或非等待模式完成。
1)Mutexes不太容易发生假性冲突。前面提到,多个哈希桶可能在同一个Latch覆盖之下。而两个用户搜索同一Latch覆盖下的两个不同哈希桶容易发生假性冲突。即因保护机制(Latch)而非用户正存取的目标对象而发生的冲突。不像Latches,Mutexes可能会为每个被保护对象创建一个Mutex。这样,因为每个被保护的结构都有一个自己的Mutex,这就意味着发生假性冲突的可能性要低得多。目前,父子游标都有自己的Mutex,用户不必为了争夺被多个游标共用的一个Latch而发生冲突。
2)替换Latches和Pins:Mutexes具有双重性。其可以作为一种串行机制(像一个Latch),同时也可以作为一个Pin(例如:阻止对象从内存中被移除)。 一个Latch不能被多个会话同时获取,而一个Mutex可以被多个会话同时参考,多个会话可以共享模式参考该Mutex。以共享模式参考一个Mutex的总会话数被称为参考数(Ref Count)。一个Mutex的参考数被存储于其自身。一个Mutex也可以独占模式被一个会话持有。
一个Mutex的参考数是对一个库缓冲Pin的替代。当使用Latches时,任何时候每个会话执行一个语句时,它首先重建并接着删除一个库缓冲Pin,而使用Mutex时,每个会话只需增加和减少相关Mutex的参考数即可(因此,参考数可替代n个不同的Pins)。这意味着在一个对象相关Mutex的参考数降为零之前,即该对象目前未被任何用户存取,该对象不能被从内存中移除。3)Mutex结构位于每个子游标句柄中,且其本身作为该游标的Pin结构。为了改变游标的Pin状态,之前我们需要获取库缓冲Latch,但现在我们可以直接修改游标Mutex的参考数。
除了每个子游标句柄中有Mutex结构且其作为游标Pin结构而带来的主要好处。如果你正有一个游标被打开(或在会话游标缓冲内),你不必获取库缓冲Latch(而之前为了改变游标Pin状态而必须这么做),就可以直接修改游标Mutex的参考数(通过会话UGA内打开游标状态区域的指针)。
所以,当钉住和释放(Pinning/unpinning)游标且无需分配和维护单独的Pin结构时,将会获得极高的可扩展性。
9. 相关注意事项
1) 库缓冲Latch机制在解析等操作中依然被需要,Mutexes仅仅解决了库缓冲的Pin问题。
2) Mutexes目前被用于库缓冲游标(而非其他类似PL/sql存储过程,表定义等)。
3)由于Mutexes是一种通用机制(而非库缓冲专用),其也被用于V$sqlSTATS底层结构。
4)当Mutexes被开启时,从X$KGLPN中将不再能看到游标Pins(由于X$KGLPN是一个基于KGL Pin数组的固定表——其将不再被用于游标)。
5)Latches和Mutexes为独立的机制,即一个进程可以同时持有Latch和Mutex。Oracle10.2.0.2以上的版本中,只要“_kks_use_mutex_pin”参数被开启,库缓冲Pin Latch将被Mutex替代,同时,其他一些类似V$sqlSTATS数组及父游标检查的操作也被Mutexes保护。然而,使用kksfbc()的真正子游标查找依然被库缓冲Latches保护,但是,频繁软解析加上很少的游标缓冲及较长的库缓冲哈希链将导致这些Latches成为一个问题(记住,即使在普通的哈希链扫描中,库缓冲Latch始终被以独占方式获取)。
Oracle 10g中,你将看到三种类型的Muxtes被使用,分别为Cursor Stat,Cursor Parent,Cursor Pin。因此,Oracle 11.2版本中去掉了所有内存和链表的小区域,事实上,该版本甚至去掉了用于保护哈希链的Latches,而代之以Mutexes。每个哈希桶被一个Mutex保护。如果在哈希桶上采用Mutexes,那就是用每个桶上一个Micro-Latch代替了最大用67个Latches覆盖131072个桶。如果在每个库缓冲对象上用一个Mutex来代表KGL Locks,而用另一个来代表KGL Pins,那将不必做所有的内存分配和释放,也不必运行代码来进行连接链表的操作。因此,从Oracle 11g版本开始,每个库缓冲桶由一个独立的Mutex来保护(对,总共131072)。
Oracle 11g版本中,有另外一些Mutexes,其中最重要的一个是库缓冲Mutex。同时,除了“library cache load lock”外,所有库缓冲相关的Latches都消失了,取而代之,相应操作都被Mutexes保护。“libarary cache”Latches已被“library cache”Mutexes替代。
下列出现于Oracle 10g版本的Latches在Oracle 11g中已不复存在:
librarycache pin allocation
librarycache lock allocation
librarycache hash chains
librarycache lock
librarycache
librarycache pin
而Oracle 11g版本中仅剩的Latches为:
librarycache load lock
下列出现于Oracle 10g版本的与库缓冲相关的等待事件在Oracle 11g版本中也不复存在:
latch:library cache
latch:library cache lock
latch:library cache pin
Oracle 11g版本中与库缓冲相关的等待事件为:
librarycache pin
librarycache lock
librarycache load lock
librarycache: mutex X
librarycache: mutex S
OSD IPClibrary
librarycache revalidation
librarycache shutdown
当然,库缓冲Mutexes还并不能解决这个世界的所有问题(特别是与过度硬解析相关的问题),例如:两个完全不同的游标依然有可能发生哈希冲突。如果一个父游标下有多个子游标,而应用疏于游标管理(例如:每次执行后关闭游标,而没有做会话游标缓冲),那么,由于连续的游标哈希链遍历而可能发生Mutex冲突。