数据库生态圈(RDB & NoSQL & Bigdata)——专注于关系库优化(Oracle & Mysql & Postgresql & SQL Server )

https://www.cnblogs.com/lhdz_bj
http://blog.itpub.net/8484829
https://blog.csdn.net/tuning_optmization
https://www.zhihu.com/people/lhdz_bj

导航

Oracle Shared Pool内部机制探讨

最近比较忙,一直没来这里了,前几天忽然想到要研究下shared pool,这个词大家可能都不陌生,但很多人包括专业DBA,也并不清楚shared pool的结构和原理,一是这部分确实是oracle内存结构中最复杂的一个部分,另一个是这方面的资料比较少,本人通过对资料的搜集、整理、翻译,最后得到下面这段文字,希望能和大家共享,同时,也希望能对大家有所启发和提高(因最近比较忙,没有时间进一步校验,文中可能存在不妥,欢迎指正):

一、简介

    什么是共享池呢?首先,让我们对共享池做一个简短的介绍。大多数人们知道共享池是SGA的一部分,这点属实,但还不仅仅这些,那么,共享池到底是什么呢?

共享池包括许多oracle的内存区域,在实例调整过程中,我们必须调整的主要区域就是共享池。如果共享池的配置不正确,整个数据库会出现性能问题。 

共享池主要和SGA的两个部分相关,一部分是固定区域,对特定版本的oracle实例来说,这部分是不变的。第二部分是可变区域,它会随着用户和应用的需求而收缩和增长。

现在我们详细看看共享池的各个组件,共享池大体可分为三个主要部分:

u       库缓冲

u       字典缓冲

u       控制结构

 

二、库缓冲

所有的SQLPL/SQL都在这部分内存中执行,所有存储的SQL语句的执行计划也会保留在这里。

我们可以进一步把库缓冲划分为:

u       共享及私有SQL

u       PL/SQL过程部分  

1、  共享及私有SQL

共享SQL区包括单条或相类似SQL语句的解析树和执行计划。Oracle通过多个类似DML语句共享一个共享SQL区来节省内存,特别是多个用户执行同一个应用的情况。

共享SQL区总是在共享池中。当SQL语句解析时,Oracle会从共享池中为其分配内存,分配内存的大小和SQL语句的复杂度有关。如果一个SQL语句要求分配一个新的共享SQL区,但共享池中没有剩余自由空间,那么,Oracle会按照最近最少使用算法(LRU)来确定从共享池中释放的内存条目,直到有足够的自由空间来满足该新的SQL共享区为止。 

私有SQL区包括类似绑定信息和运行缓冲的数据。每个发布SQL语句的会话有一个私有SQL区。每个提交相同SQL语句的用户都会有自己的私有SQL区,而这些私有SQL区共享一个共享SQL区;很多私有SQL区能和一个相同共享SQL区相关。

每个私有SQL区有一个持久区和运行时区(临时区):

1)       持久区包括跨越多次执行的绑定信息,完成数据类型转换的代码(当定义的数据类型和选择列的数据类型不一致时),还包括其他的状态信息(象递归或远程游标数或并行查询状态等)。持久区的大小和语句中绑定变量及列的数目相关。例如:如果在查询中确定了多个列,那么持久区会比较大。

2)       运行时区包括语句执行时用到的信息。运行时区的大小依赖正被执行的SQL语句的类型和复杂度,以及被语句处理的行的大小。一般来说,INSERT,UPDATEDELETE语句的运行时区比SELECT语句的稍小,特别当SELECT语句要求排序时。

作为执行请求的第一步,Oracle会创建运行时区。对INSERT,UPDATEDELETE语句,Oracle在语句执行完后释放运行时区。对查询,Oracle会在所有行被取回或查询被取消后释放运行时区。

私有SQL区的位置和会话建立连接的方式相关。如果一个会话通过专用服务器方式连接,私有SQL区在用户的PGA中分配。如果会话通过多线程服务器连接,持久区和select语句的运行时区会在SGA中。表(x$ksmms)提供特定Oracle实例的库缓冲中,有关SQL区的运行时信息。

 例如:

 

SELECT *

  FROM X$KSMSS

 WHERE KSMSSNAM = 'sql_area' AND KSMSSLEN <> 0;

 

2、  PL/SQL过程部分

Oracle处理PL/SQL程序单元(过程,函数,包,匿名块和数据库触发器)时,很大程度上和处理单条SQL语句的方式类似。

Oracle分配一个共享区来容纳解析的、编译的程序单元。Oracle分配私有区来容纳会话执行程序单元时的特定值,包括:局部,全局和包变量(也叫做包实例)及执行SQL的缓冲。如果多个用户执行同一程序单元,那么它们会使用一个共享区,而每个用户都有自己的私有SQL区,以便用来容纳每个会话的特定值。

被包含在程序单元内的各条SQL语句象以前部分描述的一样被处理。不管这些SQL语句来自哪个PL/SQL程序单元,它们都用一个共享区来容纳解析形式,而每个会话都用自己的私有区来执行语句。

    x$ksmms)表提供特定Oracle实例的库缓冲中,PL/SQL的运行时信息。例如:

 

 SELECT *

  FROM X$KSMSS

 WHERE KSMSSNAM LIKE 'PL/SQL%' AND KSMSSLEN <> 0;

 

PL/SQL MPCODE:代表依赖机器的伪码。stands for machine dependent pseudocode.

PL/SQL DIANA:代表共享池中PL/SQL代码运行时的大小。

 

3、  库缓冲管理器

库缓冲的主要目的是提供一个快速定位和存储库缓冲对象的机制。一种哈希机制用来定位一个句柄,该句柄包含对象的鉴定符(名字)。库缓冲句柄会指向一个或多个库缓冲对象及其内容。

库缓冲缓存各种库对象(例如:包,过程,函数,共享游标,匿名PL/SQL块,表定义,视图定义,表格定义等)。

库缓冲会从最顶端堆或普通SGA堆分配。当库缓冲、KGL需要更多内存时,库缓冲会调用对管理器(KGL)来进行分配。库缓冲包括一个哈希表,该哈希表包括一组哈希桶。每个哈希桶是一个库缓冲对象句柄的双向链表。每个库缓冲对象句柄指向一个库缓冲对象并有一个参照列。库缓冲对象进一步细分为其他组件,例如:依赖表,子表和授权表(这里仅举几个例子)。

 

KGH Heap Manager 说明:

    Shared poolPGA都由一个Oracle的内存管理器来管理,我们称之为KGH heap managerHeap Manager不是一个进程,而是一串代码。Heap Manager主要目的是满足server 进程请求memory 的时候分配内存或者释放内存。Heap Manager管理PGA时,需要和操作系统打交道来分配或者回收内存。但是,shared pool中,内存是预先分配的,Heap Manager管理所有的空闲内存。

    当某个进程需要在shared pool分配内存时,Heap Manager就满足该请求,Heap Manager也和其他ORACLE模块一起工作来回收shared pool的空闲内存。

 

4、  库缓冲管理器(Library Cache Manager)(哈希表和哈希桶(Hash Table and Hash Bucket)

库缓冲管理器(Library cache Manager)可以看做是堆管理器(Heap Manager)的客户端,因为库缓冲管理器通过堆管理器来分配内存来存放库缓冲对象。库缓冲管理器控制所有库缓冲对象,包括包、过程、游标、触发器等。

    库缓冲包括一个哈希表,哈希表又由哈希桶组成,哈希桶又由一些相互指向的库缓冲对象句柄组成,库缓冲对象句柄指向具体库缓冲对象以及一些引用列表。

哈希表是一组哈希桶。初始哈希桶数是251,然而,当表中的对象数超过下一个数时,桶数会增加。

下一个数是指下一个较高的质数。他们分别是25150910212039409381911638132749655211310714292967293,这里第”n+1”个大小接近第“n”个大小的两倍。哈希表膨胀的结果会导致分配下一个数大小的新哈希表,接着,把旧哈希表里的库缓冲对象重新哈希到新哈希表里,并且,释放旧哈希表的空间。整个过程,对哈希表的访问会被阻塞(通过冻结对子栓的存取),因为一个用户分配新桶使得哈希表扩大至两倍,然后,用哈希值的最低有效位数确定一个句柄属于哪个新桶。和通常的想法相反,这是个少见而不昂贵的操作,它也许会引起系统短暂(大概3-5秒)的小问题。

哈希表从来不会收缩。库缓冲管理器会对被给对象的名字空间、对象名、属主和数据库连接应用一个模哈希函数,以便确定在哪个桶里能找到该对象。

接着,会沿着链表搜索,以确定对象是否在那里。如果对象不存在,库缓冲管理器会创建一个空对象,并以给出的名字命名,把它插入到哈希表,并请求客户端调用客户端环境依赖加载函数加载它。

基本上,客户端会从磁盘上读取,调用对管理器来分配内存,并加载对象。 

5、  库缓冲句柄(Library Cache Handle

一个库缓冲句柄指向一个库缓冲对象。它包括库缓冲对象的名字、名字空间、一个时间戳、一个参照列、一个锁该对象锁的列表和一个pin该对象的pins的列表。每个对象在名字空间内通过名字唯一标识。 

    对库缓冲中所有对象的访问是通过库缓冲句柄来实现的,也就是说,我们想要访问库缓冲对象,我们必须先找到库缓冲句柄。库缓冲句柄指向库缓冲对象,它包含库缓冲对象的名字,名字空间,时间戳,引用列表,lock对象以及pin对象的列表等。

库缓冲句柄也被库缓冲用来记录哪个用户在这个句柄上有锁,或者是哪个用户正在等待获得这个锁。那么,这里我们也知道了库缓冲锁是发生在句柄上的。

    当一个进程请求库缓冲对象时,库缓冲管理器会应用一个哈希算法,得到一个哈希值,根据相应的哈希值到相应的哈希桶里去寻找。

    这里的哈希算法原理与buffer cache中快速定位block原理是一样的。如果库缓冲对象在内存中,那么这个库缓冲句柄就会被找到。有时候,shared pool不够大,库缓冲句柄会保留在内存中,而库缓冲堆由于内存不足被清出内存(age out),这时,我们请求的对象堆(object heap)就会被重载。最坏的情况下,库缓冲句柄在内存中没有找到,这时就必须分配一个新的库缓冲句柄,对象堆也会被加载到内存中。

查看namespace:

 

SYS@anqing2(rac2)> select namespace from v$librarycache;

NAMESPACE

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

SQL AREA

TABLE/PROCEDURE

BODY

TRIGGER

INDEX

CLUSTER

OBJECT

PIPE

JAVA SOURCE

JAVA RESOURCE

JAVA DATA

 

    库缓冲对象是由一些独立的堆组成,库缓冲句柄指向库缓冲对象,其实,句柄是指向第一个堆,这个堆我们就称之为heap 0Heap 0记录了指向其他heap的指针信息。

 

   

库缓冲管理器将为每个对象产生一个名字,甚至对象为匿名块。句柄用名字空间按照类型来分区库缓冲对象。

这些不同类型名字空间的例子:一个名字空间容纳所有依赖PL/SQL对象的名字空间,一个为包体和表体,一个为共享游标,一个为触发器,一个为索引,一个为簇。名字空间描述库缓冲中一个项目的类型。

名字包括对象属主名,对象名,数据库链接名和数据库属主名。通过v$librarycache可以看到一个综合列表。

如果一个句柄目前没被参照并且没被标识为被保持,那么该句柄能被释放。我们用它来确定一个句柄何时能在内存中被unpinned 

6、  库缓冲锁/pinLibrary cache lock/pin

库缓冲锁/pin用来控制对库缓冲对象的并发访问。锁管理并发,pin管理一致性,锁针对库缓冲句柄, pin针对堆。

    当我们想要访问某个库缓冲对象时,首先要获得指向这个对象的句柄的锁,获得这个锁后,我们就需要pin住指向这个对象的heap

    当我们对包、存储过程、函数、视图进行编译时,Oracle就会在这些对象的句柄上先获得一个库缓冲锁,然后,再在这些对象的heap上获得pin,这样,就保证在编译时其它进程不会来更改这些对象的定义,或者将对象删除。

    当一个sessionSQL语句进行硬解析时,该session就必须获得库缓冲锁,这样,其他session就不能访问或更改这个SQL所引用的对象。如果这个等待事件花了很长时间,通常表明共享池太小(由于共享池太小,需要搜索freechunk,或将某些可以被移出的对象清除,这样要花很长时间),当然,也有可能另外的session正在对object进行修改(比如split 分区),而当前session需要引用那个table,那么这种情况下我们必须等另外的session进行完毕。

库缓冲锁有3中模式:

u       Share(S):当读取一个库缓冲对象时获得

u       Exclusive(X):当创建/修改一个库缓冲对象时获得

u       Null(N):用来确保对象依赖性

    比如:一个进程要编译某个视图,就会获得一个共享锁,如果我们要create/drop/alter某个对象,就会获得exclusive lockNull锁非常特殊,在任何可以执行的对象(cursorfunction)上面都拥有NULL锁,我们可以随时打破这个NULL锁,当这个NULL锁被打破了,就表示这个对象被更改了,需要重新编译。

    NULL锁主要目的就是标记某个对象是否有效。比如一个SQL语句在解析的时候获得了NULL 锁,如果这个SQL的对象一直在共享池中,那么这个NULL锁就会一直存在下去,当这个SQL语句所引用的表被修改后,该NULL锁就被打破了,因为修改这个SQL语句的时候会获得Exclusive 锁,由于NULL锁被打破了,下次执行这个SQL的时候就需要重新编译。

库缓冲pin2种模式:

u       Share(S):读取object heap

u       Exclusive(X):修改object heap

    当某个session想要读取对象heap,就需要获得一个共享模式的pin,当某个session想要修改对象heap,就需要获得排他的pin。当然,在获得pin之前必须获得锁。

    Oracle10gR2中,库缓冲pin被库缓冲mutex取代。

 

7、  库缓冲闩(Library cache Latch

库缓冲闩用来控制对库缓冲对象的并发访问。前面已提到,访问库缓冲对象前必须获得库缓冲锁,锁不是一个原子操作(原子操作就是在操作程中不会被打破的操作,很明显,这里的锁可以被打破), Oracle为了保护这个锁,引入了库缓冲闩机制,也就是说,在获得库缓冲锁前,需先获得库缓冲闩,当获得库缓冲锁后就释放库缓冲闩。

    如果某个库缓冲对象没在内存中,那么这个锁就不能被获取,这时,需要获得一个库缓冲加载锁闩(library cache load lock latch),然后,再获取一个库缓冲加载锁(library cache load lock),当该加载锁获得后,释放库缓冲加载锁闩。

    库缓冲闩受隐含参数_KGL_LATCH_COUNT控制,默认值为大于等于系统中CPU个数的最小素数,但Oracle对其有一个硬性限制,该参数不能大于67

    注意:我们去查询_kgl_latch_count有时候显示为0,这是一个bug

Oracle用下面算法来确定库缓冲对象句柄由哪个子闩(latch)来保护:

    latch# = mod(bucket#, #latches)

    也就是说,用哪个子闩保护某个句柄是根据该句柄所在的桶号,以及总共有多少个子latch来进行哈希运算得到。

 

三、字典缓冲(Dictionary Cache

应用用户打算对其做查询的表定义包括表的相关索引、列及授权信息。

 

 SELECT *

  FROM X$KSMSS

 WHERE KSMSSNAM LIKE 'PL/SQL%' AND KSMSSLEN <> 0;

 

四、控制结构(Control Structure

这部分包括关于内部闩和锁(数据结构)相关的信息,也包括缓冲头(buffer header),进程会话及事务数组。

这些数组大小依赖于init.ora文件中初始化参数的设置,并且,只有关闭数据库才能改变这些数组大小。 

1、  共享池块(Shared Pool Chunks

我们仔细研究了共享池,为此,我们应该对表x$ksmsp进行仔细研究,该表中的每行都显示一个共享池块: 

 

select * from X$ksmsp

 

当分配每个共享池块时,代码被转到做分配工作的一个函数,这个地址对列KSMCHCOM是可见的,它描述了分配的目的。这个块应该比对象要大很多,因为还包含了头信息。

KSMCHCLS表示类,基本来说有四类:

    Freeabl:能被释放,只是包含了会话调用时需要的对象。

    Free :空闲且不被有效对象包含。

    Recr :被临时对象包含。

    Perm :被永久对象包含。

 

  SELECT KSMCHCLS CLASS,

         COUNT (KSMCHCLS) NUM,

         SUM (KSMCHSIZ) SIZ,

         TO_CHAR ( ( (SUM (KSMCHSIZ) / COUNT (KSMCHCLS) / 1024)), '999,999.00')

         || 'k'

            "AVG SIZE"

    FROM X$KSMSP

GROUP BY KSMCHCLS;

 

注意:

    在生产库上查询X$KSMSP时,要看下系统繁忙程度或负载高低,因为可能会导致数据库hang住。

因此总得汇总:

 

  SELECT KSMCHCOM name,

         COUNT (KSMCHCOM),

         SUM (DECODE (KSMCHCLS, 'recr', KSMCHSIZ)) RECREATABLE,

         SUM (DECODE (KSMCHCLS, 'freeabl', KSMCHSIZ)) FREEABLE

    FROM x$ksmsp

GROUP BY KSMCHCOM;

 

2、  LRU列表(LRU List

当进程启动时,会分配一些内存,当内存分配失败时,它会试着从共享内存中移去包含可重新创建对象的块,以得到需要的块大小。基于LRULeast Recent Used)从内存中移去对象意味着那些频繁被pin的对象被保持在内存中,而不频繁被pin的对象通常会被移除内存,其次,过度和其他等临时对象会被移出内存。 

ORA-04331 unable to allocate x bytes of shared pool’当所有的空闲内存被完全耗尽时(稍后,我们将讨论共享池碎片)。

有一个众所周知的被维护列表是保留列表,它一般是共享池总大小的5%,并且,保留大小通过init.ora文件里的shared_pool_reserved_size参数配置。

通过v$shared_pool_reserved视图,我们可以看到保留列表的大小,REQUEST_MISS显示大块申请丢失的次数。 

posted on 2011-08-31 13:58  lhdz_bj  阅读(1128)  评论(0编辑  收藏  举报