Oracle内存全面分析(9)

 

2.2.            Oracle 的内存的分配、回收

Oracle 中的共享内存区的分配都是以 chunk 为最小单位的。 Chunk 不是一个固定值,它是一个扩展段( extent )中一块连续的内存。而 Oracle 的内存(其他存储,如磁盘也是)的增长是以扩展段为基础的。

2 .2.1.   空闲列表和 LRU 链表

空闲的 chunk 按照大小来组织在相应的空闲列表( Free List )中。而未 pin 住的、可重建( unpinned recreatable )的 chuck 被维护在两个分别用于周期性 chunk 和短期 chunk LRU 链表中。子堆还有一个包含少许空闲内存的主永久内存 chunk

2 .2.2.   空闲内存分配和回收

空闲内存都是由空闲列表( free list )统一管理、分配的。每个空闲的 chunk (大块)都会属于也只属于一个空闲列表。空闲列表上的 chunk 的大小范围是由 bucket 来划分的。 Bucket 直译为“桶”,在西方,往往用桶来盛装一定品质范围的物品,以便查找。比如,在采矿时,用不同的桶来装不同纯度的矿石,在桶上标明矿石的纯度范围,以便在提炼时可以采用不同工艺。在这里,我们也可以把 bucket 视为一种索引,使 Oracle 在查找空闲块时,先定位所需的空闲块在哪个 bucket 的范围内,然后在相应的空闲列表中查找。

一次内存的分配过程如下:当一个进程需要一个内存的大块( chunk )时,它会先扫描目标空闲列表(每个空闲列表对应有一个 bucket bucket 是这个空闲列表的中 chunk 的大小范围)以查找最适合大小的 chunk 。如果找不到一个大小正好合适的 chunk ,则继续扫描空闲列表中更大的 chunk 。如果找到的可用 chunk 比所需的大小大 24 字节或者更多,则这个 chunk 就会被分裂,剩余的空闲 chunk 又被加到空闲列表的合适位置。如果空闲列表中没有一个 chunk 能满足要求的大小,则会从非空的相邻 bucket 的空闲列表中取最小的 chunk 。如果所有空闲列表都是空的,就需要扫描 LRU 链表释放最近最少使用的内存。当 chunk 空闲时,如果相邻的 chunk 也是空闲的,它们可能会结合( coalesce )起来。

当所有空闲列表都没有合适的空闲 chunk 可用时,就开始扫描 LRU 链表,将最近最少使用的 chunk 释放掉(如有空闲的相邻 chunk ,则结合),放到相应的空闲列表种去,直到找到合适的 chunk 或者达到最大查找数限制。如果在释放了 LRU 链表中最近最少使用的 chunk 后没没有找到合适空闲 chunk ,系统就抛 4031 错误。

如果找到了可用空闲 chunk ,就将它从空闲列表中移出,放到 LRU 链表中去。

下面介绍一下 Shared Pool 的分配和回收。

2.2.3.   Shared Pool 的分配和回收

Shared Pool 中,空闲列表扫描、管理和 chunk 分配的操作都是受到 shared pool latch 保护的。显然,如果 shared pool 含有大量的非常小的空闲 chunk ,则扫描空闲列表时间将很长,而 shared pool latch 则会要保持很久。这就是导致 shared pool latch 争用的主要原因了。有些 DBA 通过增加 shared pool 来减少 shared pool latch 争用,这种方法是没有用的,可能反倒会加剧争用(作为短期解决方法,可以 flush shared pool ;而要真正解决问题,可以采取在实例启动时 keep 住那些可能会断断续续使用的对象【这种对象最容易导致 shared pool 碎片】)。

只有执行了 ALTER SYSTEM FLUSH SHARED_POOL 才会使 shared pool 的空闲 chunk 全结合起来。因此,即使 shared pool 空闲内存之和足够大,也可能出现内存请求失败(空闲内存都是一些很小的碎片 chunk )。

实际上, oracle 实例启动时,会保留大概一半的 Shared Pool ,当有内存压力时逐渐释放它们。 Oracle 通过这种方法限制碎片的产生。 Oracle 的少量空袭内存( spare free memory )和 X$ 表即其他持久内存结构一起,隐含在 shared pool 的主要持久内存 chunk 中。这些内存不在 shared pool 的空闲列表中,因此能够立即被分配。但是,却包含在视图 V$SGASTAT free memory 的统计数据中。

SQL> select * from v$sgastat

  2  where pool = 'shared pool'

  3  and name = 'free memory';  

POOL         NAME                            BYTES

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

shared pool  free memory                  18334468  

SQL>  

spare free memory 可以用以下方式查出:

select    

  avg(v.value)  shared_pool_size,    

  greatest(avg(s.ksmsslen) - sum(p.ksmchsiz), 0)  spare_free,    

  to_char(    

    100 * greatest(avg(s.ksmsslen) - sum(p.ksmchsiz), 0) / avg(v.value),    

    '99999'    

  ) || '%' wastage    

from    

  sys.x$ksmss s,    

  sys.x$ksmsp p,    

  sys.v$parameter v    

where    

  s.inst_id = userenv('Instance') and    

  p.inst_id = userenv('Instance') and    

  p.ksmchcom = 'free memory' and    

  s.ksmssnam = 'free memory' and    

  v.name = 'shared_pool_size';      

   

注意:如果 10g 中用了 SGA 内存自动管理。以上语句可能无法查出。

当需要时,少量的空闲内存 chunk 会被释放到 shared pool 中。除非所有这些少量空闲内存倍耗尽了,否则不会报 4031 错误。如果实例在负载高峰运行了一段时期之后还有大量的少量空闲内存,这就说明 shared pool 太大了。  

而未 pin 住的、可重建( unpinned recreatable )的 chuck 被维护在两个分别用于周期性 chunk 和短期 chunk LRU 链表中。这两个 LRU 链表的长度可以通过表 X$KGHLU 查到,同时还能查到被 flush chunk 数、由于 pin unpin 而加到和从 LRU 链表中移出的 chunk 数。 X$KGHLU 还能显示 LRU 链表没有被成功 flush 的次数,以及最近一次这样的请求失败的请求大小。

SQL> column

  kghlurcr heading "RECURRENT|CHUNKS"

SQL> column kghlutrn heading "TRANSIENT|CHUNKS"

SQL> column kghlufsh heading "FLUSHED|CHUNKS"

SQL> column kghluops heading "PINS AND|RELEASES"

SQL> column kghlunfu heading "ORA-4031|ERRORS"

SQL> column kghlunfs heading "LAST ERROR|SIZE"

SQL> select

  2  kghlurcr "RECURRENT_CHUNKS",

  3  kghlutrn "TRANSIENT_CHUNKS",

  4  kghlufsh "FLUSHED_CHUNKS",

  5  kghluops "PINS AND_RELEASES",

  6  kghlunfu "ORA-4031_ERRORS",

  7  kghlunfs "LAST ERROR_SIZE"

  8  from

  9  sys.x$kghlu

  10  where

  11  inst_id = userenv('Instance');  

RECURRENT_CHUNKS TRANSIENT_CHUNKS FLUSHED_CHUNKS PINS AND_RELEASES ORA-4031_ERRORS LAST ERROR_SIZE

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

             327              368              0              7965               0               0

             865              963           2960            102138               0               0

            1473             5657             96             20546               1             540

               0                0              0                 0               0               0

如果短期链表的长度大于周期链表的长度 3 倍以上,说明 Shared Pool 太大,如果 chunk flash LRU 操作的比例大于 1/20 ,则说明 Shared Pool 可能太小。

2.3.            Oracle UNIX 下的内存管理

UNIX 下, Oracle 是以多进程的方式运行的。除了几个后台进程外, Oracle 为每个连接起一个进程(进程名称中, LOCAL = NO )。而 UNIX 下的内存分为两类,一种叫共享内存,即可以被多个进程共享;还有一种就是私有进程,只能被所分配的进程访问。更加 Oracle 内存区的不同特性, SGA 内存可以被所有会话进程共享,因此是从共享内存中分配的;而 PGA 是进程私有的,因而是从私有内存中分配的。

2 .3.1.   共享内存和信号量

Unix 下, Oracle SGA 是保存在共享内存中的(因为共享内存是可以被多个进程共享的,而 SGA 正是需要能被所有 Oracle 进程所共享)。因此,系统中必须要有足够的共享内存用于分配 SGA 。而信号量则是在共享内存被多个进程共享时,防止发生内存冲突的一种锁机制。

UNIX 下,对于内存的管理配置,是由许多内核参数控制,在安装使用 Oracle 时,这些参数一定要配置正确,否则可能导致严重的性能问题,甚至 Oracle 无法安装、启动。涉及共享内存段和信号量的参数:

参数名称

建议大小(各个平台的 Oracle 建议值可以去 metalink 上找)

参数描述

SHMMAX 

可以得到的物理内存

(0.5* 物理内存 )

定义了单个共享内存段能分配的最大数 .

SHMMAX 必须足够大 , 以在一个共享内存段中能足够分配 Oracle SGA 空间 . 这个参数设置过低会导致创建多个共享内存段 , 这会导致 Oracle 性能降低 .

SHMMIN

 

定义了单个共享内存段能分配的最小数 .

SHMMNI 

512 

定义了整个系统共享内存段的最大数。

NPROC

4096

系统的进程总数

SHMSEG

32 

定义了一个进程能获取的共享内存段的最大数 .

SEMMNS

(NPROC * 2) * 2 

设置系统中的信号数 .

SEMMNS 的默认值是 128 

SEMMNI 

(NPROC * 2) 

定义了系统中信号集的最大数

SEMMSL

Oracle 中参数 processes 大小相同

一个信号集中的信号最大数

SEMMAP 

((NPROC * 2) + 2) 

定义了信号映射入口最大数

SEMMNU 

(NPROC - 4) 

定义了信号回滚结构数

SEMVMX 

32768 

定义了信号的最大值

 

信号量 (Semaphore) :对于每个相关的 process 都給予一个信号來表示其目前的狀态。主要的目地在于 process 间能同步,避免 process 存取 shared data 时产生碰撞 ( collisions) 情況。可以把信号量视为操作系统级的用于管理共享内存的钥匙,每个进程需要有一把,一个信号量集就是一组钥匙,这组钥匙可以打开共同一个共享内存段上的 所。当一个进程需要从共享内存段中获取共享数据时,使用它自己的钥匙打开锁,进入后,再反锁以防止其他进程进来。使用完毕后,将锁释放,这样其他进程就可 以存取共享数据了。

共享内存( Shared Memory 是指同一块记内存段被一个以上的进程所共享。这是我们所知速度最快的进程间通讯方式。使用共享内存在使用多 CPU 的机器上,会使机器发挥较佳的效能。    

可以通过以下命令检查你当前的共享内存和信号量的设置:

$ sysdef | more    

Oracle 异常中止,如果怀疑共享内存没有被释放,可以用以下命令查看:

$ipcs -mop

IPC status from /dev/kmem as of Thu Jul  6 14:41:43 2006

T     

ID

     KEY        MODE        OWNER     GROUP NATTCH  CPID  LPID

Shared Memory:

m       0 0x411c29d6 --rw-rw-rw-      root      root      0   899   899

m       1 0x4e0c0002 --rw-rw-rw-      root      root      2   899   901

m       2 0x4120007a --rw-rw-rw-      root      root      2   899   901

m  458755 0x0c6629c9 --rw-r-----      root       sys      2  9113 17065

m       4 0x06347849 --rw-rw-rw-      root      root      1  1661  9150

m   65541 0xffffffff --rw-r--r--      root      root      0  1659  1659

m  524294 0x5e100011 --rw-------      root      root      1  1811  1811

851975

  0x5fe48aa4 --rw-r-----    oracle  oinstall     66  2017 25076  

然后它 ID 号清除共享内存段:

$ipcrm –m 851975  

对于信号量,可以用以下命令查看:

$ ipcs -sop

IPC status from /dev/kmem as of Thu Jul  6 14:44:16 2006

T      ID

     KEY        MODE        OWNER     GROUP

Semaphores:

s       0 0x4f1c0139 --ra-------      root      root

... ...

s      14 0x6c200ad8 --ra-ra-ra-      root      root

s      15 0x6d200ad8 --ra-ra-ra-      root      root

s      16 0x6f200ad8 --ra-ra-ra-      root      root

s      17 0xffffffff --ra-r--r--      root      root

s      18 0x410c05c7 --ra-ra-ra-      root      root

s      19 0x00446f6e --ra-r--r--      root      root

s      20 0x00446f6d --ra-r--r--      root      root

s      21 0x00000001 --ra-ra-ra-      root      root

s  

45078

  0x67e72b58 --ra-r-----    oracle  oinstall  

Oracle 异常中止,可以根据信号量 ID ,用以下命令清除信号量:

$ipcrm -s 45078  

一个共享内存段可以可以分别由以下两个属性来定位:

o        Key :一个 32 位的整数

o        共享内存 ID :系统分配的一个内存

$ ipcs -mop

IPC status from /dev/kmem as of Thu Jul  6 10:32:12 2006

T      ID     KEY        MODE        OWNER     GROUP NATTCH  CPID  LPID

Shared Memory:

m       0 0x411c29d6 --rw-rw-rw-      root      root      0   899   899

m       1 0x4e0c0002 --rw-rw-rw-      root      root      2   899   901

m       2 0x4120007a --rw-rw-rw-      root      root      2   899   901

m  458755 0x0c6629c9 --rw-r-----      root       sys      2  9113 17065

m       4 0x06347849 --rw-rw-rw-      root      root      1  1661  9150

m   65541 0xffffffff --rw-r--r--      root      root      0  1659  1659

m  524294 0x5e100011 --rw-------      root      root      1  1811  1811

m  851975 0x5fe48aa4 --rw-r-----    oracle  oinstall     66  2017 25076

2 .3.2.   私有内存

对于 PGA ,由于是分配的私有内存,不存在争用问题,因而 OS 也没有相应的信号量来控制(同理, PGA 中也没有 latch )。在 10g 之前, PGA 的私有内存是通过函数 malloc() sbrk() 进行分配扩展的, 10g 之后,私有内存是通过函 mmap() 进行初始化的。

另外,还有一点要注意的是,和 SGA 不同, PGA 内存的大小不是固定的,是可以扩展的(前者的大小是固定的,无法增长的)。因而进程的私有内存是会增长的。因此,一个规划好的系统发生内存不足的情况通常是由于进程的私有内存或进程数量突然增长造成的( PGA_AGGREGATE_TARGET 参数能尽量控制但不保证 PGA 内存总量被控制在一定数值范围内)。

隐含参数 _pga_large_extent_size 1048576 )和 _uga_cga_large_extent_size 262144 )就控制了当初始化时, PGA UGA CGA 扩张段的最大大小。

2 .3.3.   SWAP 的保留区

swap (交换)区,是 UNIX 中用来分配虚拟内存的一块特殊的磁盘分区。 UNIX 启动每一个进程,都需要在 swap 区预留一块和内存一样大小的区域,以防内存不够时作数据交换。当预留的 swap 区用完时,系统就不能再启动新的进程。比如,系统物理内存是 4G ,而设置的交换区只有 1G ,那么可以计算得出大概 3G 的内存会浪费( Buffer Cache 除外,可能有 2G 浪费)。    

HP-UX 中,参数 swapmen_on 可以让系统创建一个 pseudo-swap (伪交换区),大小为系统物理内存的 3/4 ,但是这个伪交换区并不占用任何内存和硬盘资源。只是说,让系统认为,交换区的大小是 1+4*3/4=4G ,而不是 1G ,就是说可以启动更多的进程,避免内存的浪费。

一般系统物理内存不大的时候,设置交换区是物理内存的 2-4 倍, swapmen_on 设置为 1 0 都没什么影响,但是当系统内存很大如 8G 时,因为 swap 一般不设为 16G-32G ,这时开启 swapmen_on 就很必要了。

hp 建议,即使设置了 swapmen_on ,也将你的 swap 为物理内存的 1-1.5 倍。  

swap 大小设置不当,也同样会造成系统的性能问题。因为, swap 中首先会为各个进程留出一个保留区,这部分区去掉后, swap 的可用大小就比较小了(这就是为什么用 swapinfo 可能看到 Total PCT USED 100% dev PCT USED 0 %)。当 swap 可用区不足,而由内存需要被 page out swap 区中,就需要先将 swap 区中一些页被 page in 到物理内存中去,因而导致发生交换,产生性能问题。  

swap 的使用情况可以通过 swapinfo 查看:

> swapinfo -mt

             Mb       Mb       Mb   PCT  START/      Mb

TYPE      AVAIL    USED    FREE  USED   LIMIT RESERVE  PRI  NAME

dev        4096       0    4096    0%       0       -    1  /dev/vg00/lvol2

dev        8000       0    8000    0%       0       -    1  /dev/vg00/swap2

reserve       -   12026  -12026

memory    20468   13387    7081   65%

total     32564   25413    7151   78%       -       0    -      

你可能感兴趣的:(oracle,sql,c,unix,Semaphore,扩展)