作者: fuyuncat
来源: www.HelloDBA.com
Oracle 中最常见的内存错误就是4030 和4031 错误。这两个错误分别是在分配PGA 和SGA 时,没有足够内存分配导致的。经过我们以上对Oracle 内存的了解以及对内存管理机制的浅析,可以总结在这两种错误发生时,我们该如何分析和处理。
4030 错误是由于oracle 进程在内存扩展时,无法从OS 获取到所需的内存而产生的报错。在专有服务模式下,Oracle 进程的内存包括堆栈、PGA 、UGA (从PGA 中分配)和有些进程信息;而MTS 下,UGA 是从SGA 中分配,不包括在进程的内存范围内。
PGA 的大小不是固定的,是可以扩展的。PGA 通过系统调用扩展堆数据段时,操作系统分配新的虚拟内存给进程作为PGA 扩展段。这些扩展段一般是几个KB 。只要需要,oracle 会分配几千个扩展段。然而,操作系统限制了一个进程的堆数据段的增长。在UNIX 中,这个限制一般受到OS 内核参数MAXDSIZ 限制,这是限制单个进程的。还有一个堆所有进程的虚拟内存的总的大小的限制。这个限制和swap 交换空间(虚拟内存)大小有关。如果在扩展PGA 内存时达到这些限制,就会抛4030 错误。
既然知道了4030 错误产生的可能原因,我们在分析4030 错误时,就可以从这几个方面分别收集信息进行分析,并结合Oracle 进程内存的使用情况来解决问题。
在不同的操作系统下,我们可以使用相应的工具来收集系统的内存使用情况,以判断内存是否足够:
· OpenVMS systems
可以使用show memory 查看物理内存和虚拟内存页的使用情况
Physical Memory Usage (pages): Total Free In Use Modified
Main Memory (256.00Mb) 32768 24849 7500 419
.....
Paging File Usage (blocks): Free Reservable Total
DISK$BOBBIEAXPSYS:[SYS0.SYSEXE]SWAPFILE.SYS 30720 30720 39936
DISK$BOBBIEAXPSYS:[SYS0.SYSEXE]PAGEFILE.SYS 226160 201088 249984
DISK$BOBBIE_USER3:[SYS0.PAGEFILE]PAGEFILE.SYS 462224 405296 499968
一般情况下,空闲pagefile 的之和不能小于它的总数之和的一半。而且SWAPFILE 必须是始终没有被使用的,它的空闲页必须和总页数相同。
· Windows
Windows 下可以通过任务管理器的性能页来查看内存的使用情况,也可以使用TopShow 来观察系统进程的内存使用情况
· UNIX
不同厂商的UNIX 下,各种工具的使用和统计结果可能有所不同。常用的对内存的查看工具主要有:
o TOP —— 查看物理内存和交换空间
o vmstat —— 查看物理内存和交换空间状况,以及观察是否有page out/page in
o swapon –s —— 查看交换空间情况
o swapinfo –mt —— 查看交换空间使用情况
下面是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 自己提供的工具maxmem 来检查一个进程能够分配的最大堆数据段的大小。
> maxmem
Memory starts at: 6917529027641212928 (6000000000020000)
Memory ends at: 6917529031936049152 (6000000100000000)
Memory available: 4294836224 (fffe0000)
在操作系统,往往会有对单个进程或者所有进程能够分配的内存大小做了限制。当Oracle 分配进程内存时,如果达到这些限制,也会导致4030 错误。在不同操作系统中,可以用不同方式检查系统是否有做限制。
· OpenVMS systems:
show process/id=<process id>/quota 可以显示一个进程的可用配额是多少。
· Windows
如前所述,在window 32 位系统中,进程的可用内存限制为2G (可以通过其他方式突破此限制)。而windows 下,oracle 是以一个单独进程方式运行的,它的内存包括了堆栈、SGA 、PGA 。我们可以通过任务管理器或TopShow 来检查Oracle 进程是否达到此限制。
· UNIX
可以使用命令ulimit 来查看unix 下的限制:
> ulimit -a
time(seconds) unlimited
file(blocks) unlimited
data(kbytes) 1048576
stack(kbytes) 131072
memory(kbytes) unlimited
coredump(blocks) 4194303
有些进程会做某些操作时会需要分配大量内存,如使用了PL/SQL TABLE 或者做排序时。如果这样的进程在系统中运行一段时间后,就可能导致4030 错误的产生。我们可以用以下语句来查看各个进程的内存使用情况:
select
sid,name,value
from
v$statname n,v$sesstat s
where
n.STATISTIC# = s.STATISTIC# and
name like '%ga %'
order by 3 asc;
同时,我们还可以从操作系统的角度来确认这些大量消耗内存的进程。
· OpenVMS systems:
show process/continious 可以查看各个进程的物理和虚拟内存的使用情况。
· Windows
在windows 中,由于Oracle 是以一个单独进程运行的,而由线程来服务于会话的。因此无法查看单个会话的内存占用情况。
· UNIX
UNIX 中,可以通过ps –lef|grep ora 来查看oracle 进程占用的内存情况。
在解决4030 问题时,有一点很重要,抛出4030 错误的进程并不一定是导致内存不足的进程。只不过在它请求分配内存时,内存已经不足了。很有可能在此之前就已经有大量消耗内存的进程导致内存不足。你需要找出内存消耗不断增长的进程,观察它锁进行的操作。这条语句可以查出会话进程正在执行的语句:
select sql_text
from v$sqlarea a, v$session s
where a.address = s.sql_address and s.sid = <SID>;
另外,可以做一个heapdump ,将结果发给Oracle 进行分析,
SQL> oradebug unlimit
SQL> oradebug setorapid <PID> (
通过
v$process
查到的
pid,
用
setospid
来设置
OS
中的
PID
【或者
v$process
中的
spid
】
)
SQL> oradebug dump heapdump 7 (1-PGA; 2-Shared Pool; 4-UGA; 8-CGA; 16-top CGA; 32-large pool)
SQL> alter session set events '4030 trace name heapdump level 25';
3.1.3.
解决
4030
错误的建议
如果问题是由于swap 空间不足造成的,并且由中度或者严重的page in/page out (可以用vmstat 查看),你就需要尝试降低系统整体的虚拟内存的使用(如调整SGA 大小),或者降低单个进程内存的使用(如调整sort_area_size ),或者减少进程数量(如限制processes 参数,使用MTS )。而如果page in/page out 很少或者根本没有,就可以考虑增大swap 空间。某些系统中,可以考虑使用伪交换区(如hp-ux 中,可以考虑设置swapmen_on )。
如果问题和PLSQL 操作有关,可以,1 、检查PLSQL 中的TABLE ,看看其中的数据是否全都必要,是否可以减少数据放入TABLE 中;2 、优化相关语句(比如通过调整优化器策略,使查询计划走sort 比较少的访问路径),减少sort 操作,或者减少sort_area_size (代价就是一部分sort 操作会放在磁盘上进行,降低性能)。
9i 以后可以考虑设置PGA 内存自动管理。即设置PGA_AGGREGATE_TARGET 在一定数值范围内,WORKAREA_SIZE_POLICY 设置为AUTO 。但是注意,9i 在OpenVMS 系统上、或者在MTS 模式下不支持PGA 内存自动关联。
如果是因为进程数过多导致的内存大量消耗,首先可以考虑调整客户端,减少不必要的会话连接,或者采用连接池等方式,以保持系统有稳定的连接数。如果会话非常多,且无法降低的话,可以考虑采用MTS ,以减少Oracle 进程数。
检查SGA 中的内存区是否分配过多(如shared pool 、large pool 、java pool )等,尝试减少SGA 的内存大小。
在windows 下,可以尝试使用ORASTACK 来减少线程的堆栈大小,以释放更多的内存。
考虑增加物理内存。
4031 错误是Oracle 在没有足够的连续空闲空间分配给Shared Pool 或者Large Pool 时抛出的错误。
前面我们描述Shared Pool 的空闲空间的请求、分配过程。在受到空闲空间请求时,内存管理模块会先查找空闲列表,看是否有合适的空闲chunk ,如果没有,则尝试从LRU 链表中寻找可释放的chunk ,最终还未找到合适的空闲chunk 就会抛出4031 错误。
在讨论4031 问题之前,可以先到第一章中找到与shared pool (shared_pool_size 、shared_pool_reserved_size 、shared_pool_reserved_min_alloc )和large pool (large_pool_size )的参数描述,再了解一下这些参数的作用。这对于理解和分析4031 错误会很有帮助。此外,还需要再回顾以下10g 以后的SGA 内存自动关联部分(相关参数是SGA_TARGET ),因为使用这一特性,能大大减少4031 错误产生的几率。
通常,大多数的4031 错误都是和shared pool 相关的。因此,4031 错误的分析,主要是对shared pool 的分析。
当4031 错误提示是shared pool 无足够连续内存可分配时,有可能是由于shared pool 不足或者shared pool 中严重的碎片导致的。
· Shared pool 不足分析
视图V$SHARED_POOL_RESERVED 中可以查询到产生4031 的一些统计数据、以及shared pool 中保留区(前面说了,保留区是用来缓存超过一定大小的对象的shared pool 区)的统计信息。
如果字段REQUEST_FAILURES >= 0 并且字段LAST_FAILURE_SIZE < _SHARED_POOL_RESERVED_MIN_ALLOC ,可以考虑减小_SHARED_POOL_RESERVED_MIN_ALLOC ,以使更多的对象能放到保留区中区(当然,你还需要观察字段MAX_USED_SPACE 以确保保留区足够大)。如果还没有效果,就需要考虑增加shared_pool_size 了。
· 碎片问题分析
Library cache 和shared pool 保留区的碎片也会导致4031 错误的产生。
还是观察上面的视图,如果字段REQUEST_FAILURES > 0 并且字段LAST_FAILURE_SIZE > _SHARED_POOL_RESERVED_MIN_ALLOC ,就可以考虑增加_SHARED_POOL_RESERVED_MIN_ALLOC 大小以减少放入保留区的对象,或者增加SHARED_POOL_RESERVED_SIZE 和shared_pool_size (因为保留区是从shared pool 中分配的)的大小。
此外,要注意有一个bug 导致REQUEST_FAILURES 在9.2.0.7 之前所有版本和10.1.0.4 之前的10g 版本中统计的数据是错误的,这时可以观察最后一次4031 报错信息中提示的无法分配的内存大小。
Large pool 是在MTS 、或并行查询、或备份恢复中存放某些大对象的。可以通过视图v$sgastat 来观察large pool 的使用情况和空闲情况。
而在MTS 模式中,sort_area_retained_size 是从large pool 中分配的。因此也要检查和调整这个参数的大小,并找出产生大量sort 的会话,调整语句,减少其中的sort 操作。
MTS 中,UGA 也是从large pool 中分配的,因此还需要观察UGA 的使用情况。不过要注意一点的是,如果UGA 无法从large pool 获取到足够内存,会尝试从shared pool 中去分配。
根据4031 产生的不同原因,采取相应办法解决问题。
有很多4031 错误都是由于oracle bug 引起的。因此,发生4031 错误后,先检查是否你的系统的4031 错误是否是由bug 引起的。下面是已经发现的会引起4031 错误的bug 。相关信息可以根据bug 号或note 号到metalink 上查找。
BUG |
说明 |
修正版本 |
Bug 1397603 |
ORA-4031 由于缓存句柄导致的SGA 永久内存泄漏 |
8172, 901 |
Bug 1640583 |
ORA-4031 due to leak / 由于查询计划中AND-EQUAL 访问路径导致缓冲内存链争用,从而发生内存泄漏。 |
8171, 901 |
Bug:1318267 |
如果设置了TIMED_STATISTICS 可能导致INSERT AS SELECT 无法被共享。 |
8171, 8200 |
Bug:1193003 |
Oracle 8.1 中,某些游标不共享。 |
8162, 8170, 901 |
Bug 2104071 |
ORA-4031 太多PIN 导致shared pool 消耗过大。 |
8174, 9013, 9201 |
Note 263791.1 |
许多与4031 相关的错误在9205 补丁集中修正。 |
9205 |
大多数4031 错误都是由shared pool 不足导致的。可以从以下几个方面来考虑是否调整shared pool 大小:
· Library cache 命中率
通过以下语句可以查出系统的library cache 命中率:
SELECT SUM(PINS) "EXECUTIONS",
SUM(RELOADS) "CACHE MISSES WHILE EXECUTING",
1 - SUM(RELOADS)/SUM(PINS)
FROM V$LIBRARYCACHE;
如果命中率小于99 %,就可以考虑增加shared pool 以提高library cache 的命中率。
· 计算shared pool 的大小
以下语句可以查看shared pool 的使用情况
select sum(bytes) from v$sgastat
where pool='shared pool'
and name != 'free memory';
专用服务模式下,以下语句查看cache 在内存中的对象的大小,
select sum(sharable_mem) from v$db_object_cache;
专用服务模式下,以下语句查看SQL 占用的内存大小,
select sum(sharable_mem) from v$sqlarea;
Oracle 需要为保存每个打开的游标分配大概250 字节的内存,以下语句可以计算这部分内存的占用情况,
select sum(250 * users_opening) from v$sqlarea;
此外,在我们文章的前面部分有多处提到了如何分析shared pool 是否过大或过小,这里就不在赘述。
每当需要执行一个SQL 或者PLSQL 语句时,都需要从library cache 中分配一块连续的空闲空间来解析语句。Oracle 首先扫描shared pool 查找空闲内存,如果没有发现大小正好合适的空闲chunk ,就查找更大的chunk ,如果找到比请求的大小更大的空闲chunk ,则将它分裂,多余部分继续放到空闲列表中。这样就产生了碎片问题。系统经过长时间运行后,就会产生大量小的内存碎片。当请求分配一个较大的内存块时,尽管shared pool 总空闲空间还很大,但是没有一个单独的连续空闲块能满足需要。这时,就可能产生4031 错误。
如果检查发现shared_pool_size 足够大,那4031 错误一般就是由于碎片太多引起的。
如果4031 是由碎片问题导致的,就需要弄清楚导致碎片的原因,采取措施,减少碎片的产生。以下是可能产生碎片的一些潜在因素:
o 没有使用共享SQL ;
o 过多的没有必要的解析调用(软解析);
o 没有使用绑定变量。
以下表/ 视图、语句可以查询shared pool 中没有共享的SQL
· 通过V$SQLAREA 视图
前面我们介绍过这个视图,它可以查看到每一个SQL 语句的相关信息。以下语句可以查出没有共享的语句,
SELECT substr(sql_text,1,40) "SQL",
count(*) ,
sum(executions) "TotExecs"
FROM v$sqlarea
WHERE executions < 5 –-语句执行次数
GROUP BY substr(sql_text,1,40)
HAVING count(*) > 30 –-所有未共享的语句的总的执行次数
ORDER BY 2;
· X$KSMLRU 表
这张表保存了对shared pool 的分配所导致的shared pool 中的对象被清出的记录。可以通过它来查找是什么导致了大的shared pool 分配请求。
如果有许多对象定期会被从shared pool 中被清出,会导致响应时间太长和library cache latch 争用问题。
不过要注意一点,每当查询过表X$KSMLRU 后,它的内容就会被删除。因此,最好将查出的数据保存在一个临时的表中。以下语句查询X$KSMLRU 中的内容,
SELECT * FROM X$KSMLRU WHERE ksmlrsiz > 0;
· X$KSMSP 表
从这张表中可以查到当前分配了多少空闲空间,这对于分析碎片问题很有帮助。一些语句可以查询shared pool 的空闲列表中chunk 的统计信息,
select '0 (<140)' BUCKET, KSMCHCLS, KSMCHIDX, 10*trunc(KSMCHSIZ/10) "From",
count(*) "Count" , max(KSMCHSIZ) "Biggest",
trunc(avg(KSMCHSIZ)) "AvgSize", trunc(sum(KSMCHSIZ)) "Total"
from x$ksmsp
where KSMCHSIZ<140
and KSMCHCLS='free'
group by KSMCHCLS, KSMCHIDX, 10*trunc(KSMCHSIZ/10)
UNION ALL
select '1 (140-267)' BUCKET, KSMCHCLS, KSMCHIDX,20*trunc(KSMCHSIZ/20) ,
count(*) , max(KSMCHSIZ) ,
trunc(avg(KSMCHSIZ)) "AvgSize", trunc(sum(KSMCHSIZ)) "Total"
from x$ksmsp
where KSMCHSIZ between 140 and 267
and KSMCHCLS='free'
group by KSMCHCLS, KSMCHIDX, 20*trunc(KSMCHSIZ/20)
UNION ALL
select '2 (268-523)' BUCKET, KSMCHCLS, KSMCHIDX, 50*trunc(KSMCHSIZ/50) ,
count(*) , max(KSMCHSIZ) ,
trunc(avg(KSMCHSIZ)) "AvgSize", trunc(sum(KSMCHSIZ)) "Total"
from x$ksmsp
where KSMCHSIZ between 268 and 523
and KSMCHCLS='free'
group by KSMCHCLS, KSMCHIDX, 50*trunc(KSMCHSIZ/50)
UNION ALL
select '3-5 (524-4107)' BUCKET, KSMCHCLS, KSMCHIDX, 500*trunc(KSMCHSIZ/500) ,
count(*) , max(KSMCHSIZ) ,
trunc(avg(KSMCHSIZ)) "AvgSize", trunc(sum(KSMCHSIZ)) "Total"
from x$ksmsp
where KSMCHSIZ between 524 and 4107
and KSMCHCLS='free'
group by KSMCHCLS, KSMCHIDX, 500*trunc(KSMCHSIZ/500)
UNION ALL
select '6+ (4108+)' BUCKET, KSMCHCLS, KSMCHIDX, 1000*trunc(KSMCHSIZ/1000) ,
count(*) , max(KSMCHSIZ) ,
trunc(avg(KSMCHSIZ)) "AvgSize", trunc(sum(KSMCHSIZ)) "Total"
from x$ksmsp
where KSMCHSIZ >= 4108
and KSMCHCLS='free'
group by KSMCHCLS, KSMCHIDX, 1000*trunc(KSMCHSIZ/1000);
如果使用ORADEBUG 将shared pool 信息dump 出来,就会发现这个查询结果和trace 文件中空闲列表信息一直。
如果以上查询结果显示大多数空闲chunk 都在bucket 比较小的空闲列表中,则说明系统存在碎片问题。
当编译java (用loadjava 或deployjb )代码时产生了4031 错误,错误信息一般如下:
A SQL exception occurred while compiling: :
ORA-04031: unable to allocate bytes of shared memory ("shared pool","unknown object","joxlod: init h", "JOX: ioc_allocate_pal")
这里提示时shared pool 不足,其实是错误,实际应该是java pool 不足导致的。解决方法将JAVA_POOL_SIZE 加大,然后重启实例。
Large pool 是在MTS 、或并行查询、或备份恢复中存放某些大对象的。但和shared pool 中的保留区(用于存放shared pool 的大对象)不同,large pool 是没有LRU 链表的,而后者使用的是shared pool 的LRU 链表。
在large pool 中的对象永远不会被清出的,因此不存在碎片问题。当由于large pool 不足导致4031 错误时,可以先通过v$sgastat 查看large pool 的使用情况,
SELECT pool,name,bytes FROM v$sgastat where pool = 'large pool';
或者做一个dump ,看看large pool 中空闲chunk 的大小情况。
进入large pool 的大小条件是由参数LARGE_POOL_MIN_ALLOC 决定的,根据以上信息,可以适当调整LARGE_POOL_MIN_ALLOC 的大小。
Large pool 的大小是由LARGE_POOL_SIZE 控制的,因此当large pool 空间不足时,可以调整这个参数。
10g 以后,Oracle 提供了一个非常有用的特性,即SGA 内存自动管理。通过设置SGA_TARGET 可以指定总的SGA 大小,而无需固定每个区的大小。这就是说,当分配shared pool 或large pool 时,只要SGA 区足够大,就能获取到足够内存,因而可以大大减少4031 错误发生的几率。
使用绑定变量是解决shared pool 碎片的最好方法。此外,9i 以后,可以设置CURSOR_SHARING 为FORCE ,强行将没有使用绑定变量的语句使用绑定变量,从而共享SQL 游标。当采用以上措施后,碎片问题并不会马上消失,并可能还会长时间存在。这时,可以考虑flush shared pool ,将内存碎片结合起来。但是,在做flush 之前,要考虑以下问题。
· Flush 会将所有没有使用的游标从library cache 中清除出去。因此,这些语句在被再次调用时会被重新硬解析,从而提高CPU 的占用率和latch 争用;
· 如果应用没有使用绑定变量,即使flush 了shared pool 以后,经过一段时间运行,仍然会出现大量碎片。因此,这种情况下,flush 是没有必要的,需要先考虑优化应用系统;
· 如果shared pool 非常大,flush 操作可能会导致系统被hung 住。
因此,如果要flush shared pool ,需要在系统不忙的时候去做。Flush 的语法为,
alter system flush shared_pool;
如果问题比较复杂(比如由于内存泄漏导致),或者你不幸遇上了oracle 的bug ,这时就需要考虑设置4031 事件来trace 并dump 出相关内存信息。
以下语句在整个系统设置4031 事件,
SQL> alter system set events '4031 trace name errorstack level 3';
SQL> alter system set events '4031 trace name HEAPDUMP level 3';
这个事件也可以在会话中设置,只要将以上语句中的“system ”改为“session ”就行了。
然后将dump 出来的trace 文件发给oracle 吧。
不过注意一点,9205 以后就无需设置这个事件了,因为一旦发生4031 错误时,oracle 会自动dump 出trace 文件。