zhuqibs
作者zhuqibs·2020-04-26 09:47
软件开发工程师·Adidas

Oracle DBA 应知应会 -- 共享池HEAP的内部结构

字数 6377阅读 716评论 0赞 7

要想深入分析共享池内存的管理,我们必须知道Oracle的内存空间分配都是采用HEAP的模式,HEAP管理的基础就是KGH。因此本节将会介绍一些稍微深入一点的Oracle KGH的基本原理。本节可能有些枯燥,甚至对于某些人来说可能有点难,不过没关系,如果你真的没有兴趣看下去,你可以直接跳过本节,跳过本节只是会对共享池内部空间管理的一些深入的算法的理解产生一些偏差,不会导致你无法理解共享池的基本算法,因此可以说,本节是专门为希望了解Oracle 内存管理细节的资深人士准备的,如果你暂时无法理解,完全可以忽略。另外本节涉及的内容对于没有开发经验的DBA来说,可能会有些困难。Oracle 中最常见的内存HEAP包括SGA HEAP,PGA HEAP,另外我们最常见的表也是HEAP TABLE,其空间管理的基本概念也是使用KGH。
Oracle的各种内存组织都是以HEAP形式的,每个HEAP包含一个HEAP句柄和一系列的内存EXTENT,每个EXTENT包含了一系列的连续的CHUNK。内存申请者通过在HEAP上申请空间的模式来获得内存。我们常见的SGA,PGA都是以HEAP的形式管理的。在HEAP上分配空间(CHUNK),根据ALLOCATION CLASS的不同,其管理模式也不同。CHUNK是一个连续的内存片段,这些内存片段可以分为以下几类:
n PERMANENT:这类的CHUNK是通过KGHACPERM标志来分配的,这类CHUNK一旦分配后在整个HEAP的生命周期里不能被UNPIN和释放。一些常用的,容易产生内存碎片的CHUNK一般采用PERMANENT方式分配。
n FREEABLE:这类的CHUNK是通过KGHACFREE标志来分配的,一旦使用完毕后,立即可以使用KGHFRE()来释放。
n RECREATABLE:这类CHUNK是通过KGHACRECR标志来分配的,这类CHUNK可以被PIN或者UNPIN。调用者可以使用KGHPIR来PIN住这个CHUNK,也可以通过KGHUPR来UNPIN这个CHUNK。当这种CHUNK被UNPIN后,通过LRU机制进行管理。当调用kghupr()的时候,调用者会传递一个LATCH作为参数给HEAP MANAGER,HEAP MANAGER在使用kghfre()释放这个CHUNK的时候,会使用这个LATCH。
n FREEABLE WITH MARK:类似于FREEABLE,不同的是这类CHUNK包含一个KGHMRK(),当CHUNK使用的空间达到到KGHMRK()对应的值的时候,该CHUNK才能被释放。
另外,对于每个HEAP,不管其类型如何,HEAP都可以拥有子HEAP,比如SHARED POOL是SGA HEAP的子HEAP。
下面我们来看看HEAP是如何被创建的。HEAP创建包含两个步骤,一是初始化HEAP的句柄,二是给HEAP分配空间。当一个HEAP创建的时候,首先通过KGHINI()来初始化HEAP句柄。一个HEAP句柄包括一组FREE LIST BUCKETS和相应的SIZE参数。调用KGHINI的时候需要使用两个十分重要的宏:KGHDS和KGHDSSIZ,KGHDS定义了HEAP的FREE LIST BUCKETS的数量,KGHDSSIZ定义了句柄的大小。调用kghini()的重要参数包括:
l HEAP的扩展大小(EXTENT_SIZE)
l HEAP的FREELIST BUCKETS的数量(FREE_NUM)
l 包含每个FREE LIST BUCKETS的大小的数组(FREE_SIZE)
l 包含每个FREELIST BUCKETS类型的数组(FREE_TYPE)
每个FREE LIST包含相同的ALLOCATION CLASS的CHUNK。当一个CHUNK释放的时候,会被放入到大小小于等于该CHUNK的FREE LIST上去,在FREE LIST里,CHUNK不按照大小排序。
通过kghini的参数我们可以猜测Oracle HEAP的基本思路,比如说我们从SGA中分配一个HEAP用于SHARED POOL,在分配SHARED POOL的时候,需要确定每个HEAP的EXTENT的大小。另外我们需要知道HEAP的FREELIST BUCKETS的数量,这一点很多DBA在学习共享池内部原理的时候都已经有所认识,对于Oracle 8i或者更早的版本,共享池的free list包含10个BUCKETS,从9i开始,free lists包含256个BUCKETS。同时我们需要一个数组,来指明free list buckets中每个bucket存放的chunk的大小,同时用一个数组来存放每个bucket的FREE类型。通过这些参数,调用kghini就可以从父HEAP中分配内存了。分配的内存会被挂载到free list上,这时候HEAP就处于可用的初始化状态了。
下面我们通过一个ORACLE 8.1.5的共享池的DUMP信息来验证上面的一些概念:
FREE LISTS:
Bucket 0 size=44
Chunk 52fb1c4 sz= 60 free
Chunk 5161394 sz= 40 free
Chunk 5164904 sz= 48 free
Chunk 516fbcc sz= 28 free
Chunk 5195570 sz= 52 free
......
Bucket 1 size=76
Chunk 5180938 sz= 76 free
Chunk 51cb58c sz= 84 free
Chunk 51bf194 sz= 88 free
Chunk 51af3e0 sz= 84 free
Chunk 51ac298 sz= 76 free
Chunk 51c3818 sz= 76 free
Chunk 51f04d0 sz= 92 free
Chunk 5212c90 sz= 92 free
Chunk 529b280 sz= 76 free
Chunk 52c2248 sz= 84 free
Chunk 52c3784 sz= 80 free
Chunk 52dbb5c sz= 92 free
Chunk 52df980 sz= 92 free
Chunk 52e0aa4 sz= 88 free
Bucket 2 size=140
Bucket 3 size=268
Chunk 516325c sz= 472 free
Bucket 4 size=524
Bucket 5 size=1036
Chunk 5160be8 sz= 1080 free
Bucket 6 size=2060
Chunk 516d3cc sz= 2812 free
Bucket 7 size=4108
Bucket 8 size=8204
Bucket 9 size=16396
Bucket 10 size=32780
Chunk 4c69598 sz= 5204008 free
Total free space = 5212456
我们可以看到,共享池的FREE LIST共有10个BUCKETS,其中BUCKET0是存放所有小余76字节的CHUNK,BUCKET 10存放所有大于32780字节的CHUNK。其他BUCKET存放的CHUNK都是大于其SIZE大小,小于下一个BUCKET的SIZE大小的CHUNK。

如果需要从CHUNK中分配空间,可以通过调用kghalo()来实现,这个调用返回指向CHUNK空间的指针。在调用kghalo的时候,需要分配的空间的大小通过REQ_SIZE参数传递,但是HEAP MANAGER不一定分配申请的大小空间,而是根据判断,分配一个被称为ACTUAL_SIZE的空间,ACTUAL_SIZE可能比REQ_SIZE大或者小,这一切都是根据当前拥有的空间的情况,以及此类CHUNK的分配特性来进行 断的,并不是无规律的。从CHUNK中分配空间的基本顺序如下:
l 首先搜索FREELIST上是否存在可以FREE的CHUNK
l 如果没找到,第二步查找是否有RECREATABLE的CHUNK可以释放。释放已经使用的CHUNK,需要使用到另外一个链表,就是LRU链表,LRU链表中根据使用频繁程度,通过一个双向链来串联已经被分配的CHUNK。如果在LRU链中找到了可以释放的CHUNK,就通知HEAP MANAGER去释放,如果该CHUNK是被PIN住的,首先要UNPIN这些CHUNK,然后才能释放。HEAP MANAGER在释放内存时,会自动合并碎片。实际上这个释放RECREATEABLE的CHUNK的过程和我们做FLUSH SHARED_POOL操作是类似的,只是其规模要小的多,HEAP MANAGER释放CHUNK的时候有一个条件,一旦释放后已经存在足够分配的CHUNK,那么这个释放过程就会结束
l 如果第二步还没找到可以释放的CHUNK,就从父HEAP里分配空闲的空间
l 如果父HEAP无法分配空间,那么就会报错
在分配空间的时候,如果要在FREELIST中查找,那么首先会根据需要分配空间的大小,找到相应的BUCKET(每个BUCKET都有一个最大大小,指明该BUCKET中的空闲CHUNK不会超过某个特定的值,找到最大大小<=需要分配大小的最大的那个BUCKETS),然后搜索整个链表,如果找到了正好相同大小的CHUNK,那么就直接分配使用,如果找不到合适的CHUNK,就会选择一个稍大的CHUNK,然后分裂空间,将该CHUNK分裂为两个CHUNK,一个和所分配空间大小一致,一个是剩下的空间。然后将剩下的空间根据大小挂载到相应的BUCKET的双向链表上。
如果要释放一个EXTENT的空间,可以通过kghfre()调用来实现。在调用这个调用的时候,要让所有的正在使用这个EXTENT的会话完成执行,释放或者UNPIN相关的对象,然后才能释放EXTENT。
要释放HEAP中的所有EXTENT,可以调用KGHFRH()。不过在释放HEAP之前,必须先释放所有的子HEAP。在子HEAP里释放所有的EXTENT,可以调用KGHFRU()。
上面的这些文字,对于没有任何程序开发经验的人来说,确实有些难以理解。其实老白已经将HEAP的算法做了最大程度的精简,否则大家看得就更是一头雾水了。DBA理解HEAP的一些基本算法有什么好处呢?实际上,我们平时对于Oracle 的HEAP管理,特别是共享池的管理,存在一些误区,而通过对HEAP的一些基本原理和算法的理解,可以帮助我们纠正这些误区。
首先是共享池碎片化的问题,根据HEAP的内存管理方法,我们很容易可以看出,其实碎片化是不可避免的,共享池使用一段时间之后,肯定会出现碎片化的趋势,而且随着数据库实例的启动时间的增长,这种碎片化趋势会越来越明显。但是共享池碎片化并不等于共享池就有问题,虽然说碎片化的共享池的效率可能会有所下降,但是一般情况下都在可以接受的范围内,因此DBA也不需要担心这个问题,不要谈虎色变。
接下来要讨论多的一个问题是共享池的使用率的问题。很多DBA看到共享池使用率比较高,就十分紧张,共享池快用完了,会不会出问题。如果你了解了HEAP的内存分配和管理策略,这个担心就不必要了。既然分配了那么多的共享池,那就尽可能的用吧,只有把共享池全部用掉了,才能充分达到共享CURSOR和字典的好处。反而如果共享池使用率不高,那才是头痛的事情,资源不能充分的利用。因此我们不能通过共享池的使用率来判断共享池是否存在隐患。有时候共享池使用率只有50%,但是可能性能并不好,而有时候共享池总是接近100%的使用率,但是共享池没有任何问题。
第三个要讨论的问题就是,如果共享池的争用很厉害,但是共享池的使用率并不高,说明什么问题?这个问题也是困扰了老白很多年的问题,老白也经常碰到一些系统,共享池相关闩锁争用十分严重,大多数情况下,共享池争用严重是由于共享池内存不足导致的。但是有些时候碰到这些问题,进行分析的时候发现共享池的使用率并不高,只有60%左右。当老白了解了共享池内存分配算法后,才想明白了这其中的道理。如果内存分配十分频繁,那么共享池的FREELIST上的CHUNK会被优先使用,那么共享池的使用率就会持续上升。而如果共享池使用率不高,那么就说明共享池争用并不是由于分配内存十分频繁导致。如果不是分配内存,那是怎么回事呢?我想刚刚看了本节的有些朋友已经有答案了,十分频繁的不分配内存的访问,肯定是访问可重用的数据了。什么情况可能出现这样的争用呢?这个问题应该很容易吧,软分析是十分典型的这类访问。于是问题就变得简单了,减少软分析就可以缓解这个问题。那么,加大SESSION_CACHED_CURSORS吧,老白把这个参数调整到200,再一分析,共享池的使用率达到了90%,共享池的争用减少了。
第四个问题是什么情况可能导致共享池出现ORA-4031呢?老白最初接触共享池的时候,总觉得共享池既然是可以通过LRU算法换出不常用的,那么应该不会出现不足的情况啊。随着对共享池内存分配和管理算法的研究的深入,老白发现,在释放共享池内存的时候,是有条件的,当前被PIN住的内存是不能释放的。如果某个CURSOR,当前正好有个会话正在执行,那么这个CURSOR相关的所有内存对象必须是被PIN住的,不能随便释放,否则这个SQL的执行就会出现问题。正因为这样,如果系统并发量很大的时候,很多共享池的内存是被PIN住,无法释放的,这样,共享池的空闲空间就被分给为无数个很小的碎片,对于一些较大的分配,可能就无法满足,出现ORA-4031也就不可避免了。这种情况,我们可以认为共享池不足导致了问题,如果共享池的空间再大一点,出现内存不足的可能性也就会下降。除了共享池不足,还有什么情况可能导致ORA-4031呢?前面我们说的那种情况,是由于系统并发量很大,大量的共享池内存被PIN住而无法释放。这种情况,一旦系统负载下降,共享池就会逐渐恢复正常。另外一种情况就是,共享池中的有些LIST对象(比如LOCK,RESOURCE等),初始化分配一部分空间,如果这部分列表用完了,可以动态扩展。由于这些扩展是永久性的,所以,这些扩展就像是共享池中钉下的一颗颗的钉子,是无法拔掉的。如果这类的扩展十分频繁,那么时间长了,共享池就会被这些钉子分割为很多碎片。这种碎片化是永久性的,随着实例启动时间的推移,这种碎片化会越来越严重,而且无法自动或者手工修复。积累到一定程度,这个系统就会出现严重的碎片化问题,从而导致出现ORA-4031,甚至导致宕机。对于这种系统,一般来说可以通过三个渠道来优化:第一个渠道是加大共享池,使之碎片化的时间推迟,但是这只是延迟碎片化,不能解决碎片化;第二个渠道是定期重启实例,通过重启实例,恢复共享池的内存空间,确保系统正常运行,不出现宕机现象,不过要做到这一点,对于大多数7×24的系统来说十分困难;第三个是扩大经常动态扩展的列表对象的初始大小,减少其动态扩展的次数,最好能够让它们不再动态扩展,这个做法会浪费一定的共享池空间,但是是可以解决这个问题的最好的方法。

如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!

7

添加新评论0 条评论

Ctrl+Enter 发表

作者其他文章

相关文章

相关问题

相关资料

X社区推广