第4章 内存结构
这一章将讨论Oracle的3个主要的内存结构:
q 系统全局区(System Global Area,SGA):这是一个很大的共享内存段,几乎所有Oracle进程都要访问这个区中的某一点。
q 进程全局区(Process Global Area,PGA):这是一个进程或线程专用的内存,其他进程/线程不能访问。
q 用户全局区(User Global Area,UGA):这个内存区与特定的会话相关联。它可能在SGA中分配,也可能在PGA中分配,这取决于是用共享服务器还是用专用服务器来连接数据库。如果使用共享服务器,UGA就在SGA中分配;如果使用专用服务器,UGA就会在PGA(即进程内存区)中。
注意 在Oracle的较早版本中,共享服务器称为多线程服务器(Multi-Threaded Server)或MTS。这本书中我们会一直用“共享服务器”的说法。
下面首先讨论PGA和UGA,然后再来介绍SGA,SGA确实是一个很庞大的结构。
4.1 进程全局区和用户全局区
进程全局区(PGA)是特定于进程的一段内存。换句话说,这是一个操作系统进程或线程专用的内存,不允许系统中的其他进程或线程访问。PGA一般通过C语言的运行时调用malloc()或memmap()来分配,而且可以在运行时动态扩大(甚至可以收缩)。PGA绝对不会在Oracle的SGA中分配,而总是由进程或线程在本地分配。
实际上,对你来说,用户全局区(UGA)就是你的会话的状态。你的会话总能访问这部分内存。UGA的位置完全取决于你如何连接Oracle。如果通过一个共享服务器连接,UGA肯定存储在每个共享服务器进程都能访问的一个内存结构中,也就是SGA中。如果是这样,你的会话可以使用任何共享服务器,因为任何一个共享服务器都能读写你的会话的数据。另一方面,如果使用一个专用服务器连接,则不再需要大家都能访问你的会话状态,UGA几乎成了PGA的同义词;实际上,UGA就包含在专用服务器的PGA中。查看系统统计信息时可以看到,采用专用服务器模式时,总是会报告UGA在PGA中(PGA大于或等于所用的UGA内存;而且PGA内存的大小会包括UGA的大小)。
所以,PGA包含进程内存,还可能包含UGA。PGA内存中的其他区通常用于完成内存中的排序、位图合并以及散列。可以肯定地说,除了UGA内存,这些区在PGA中的比重最大。
从Oracle9i Release 1起,有两种办法来管理PGA中的这些非UGA内存:
q 手动PGA内存管理,采用这种方法时,你要告诉Oracle:如果一个特定进程中需要排序或散列,允许使用多少内存来完成这些排序或散列。
q 自动PGA内存管理,这要求你告诉Oracle:在系统范围内可以使用多少内存。
分配和使用内存的方式因情况不同而有很大的差异,因此,我们将分别进行讨论。需要说明,在Oracle9i中,如果采用共享服务器连接,就只能使用手动PGA内存管理。这个限制到Oracle
PGA内存管理受数据库初始化参数WORKAREA_SIZE_POLICY的控制,而且可以在会话级修改。在Oracle9i Release 2及以上版本中,这个初始化参数默认为AUTO,表示自动PGA内存管理。而在Oracle9i Release 1中,这个参数的默认设置为MANUAL。
下面几节将分别讨论这两种方法。
4.1.1 手动PGA内存管理
如果采用手动PGA内存管理,有些参数对PGA大小的影响最大,这是指PGA中除了会话为PL/SQL表和其他变量分配的内存以外的部分,这些参数如下:
q SORT_AREA_SIZE:在信息换出到磁盘之前,用于对信息排序的RAM总量。
q SORT_AREA_RETAINED_SIZE:排序完成后用于保存已排序数据的内存总量。也就是说,如果SORT_AREA_SIZE是512 KB,SORT_AREA_RETAINED_SIZE是256 KB,那么服务器进程最初处理查询时会用512 KB的内存对数据排序。等到排序完成时,排序区会“收缩”为256 KB,这256 KB内存中放不下的已排序数据会写出到临时表空间中。
q HASH_AREA_SIZE:服务器进程在内存中存储散列表所用的内存量。散列联结时会使用这些散列表结构,通常把一个大集合与另一个集合联结时就会用到这些结构。两个集合中较小的一个会散列到内存中,散列区中放不下的部分都会通过联结键存储在临时表空间中。
Oracle将数据写至磁盘(或换出到磁盘)之前,数据排序或散列所用的空间量就由这些参数控制,这些参数还控制着排序完成后会保留多少内存段。SORT_AREA_SIZE~SORT_AREA_ RETAINED_SIZE这部分内存一般从PGA分配,SORT_AREA_RETAINED_SIZE这部分内存会在UGA中分配。通过查询一些特殊的Oracle V$视图,可以看到PGA和UGA内存的当前使用情况,并监视其大小的变化,这些特殊的V$视图也称为动态性能视图(dynamic performance view)。
例如,下面来运行一个小测试,这里会在一个会话中对大量数据排序,在第二个会话中,我们将监视第一个会话中UGA/PGA内存的使用。为了以一种可预测的方式完成这个工作,我们建立了ALL_OBJECTS表的一个副本,其中有大约45 000行,而且没有任何索引(这样就能知道肯定会发生排序):
ops$tkyte@ORA
Table created.
ops$tkyte@ORA
PL/SQL procedure successfully completed. |
为了消除最初硬解析查询所带来的副作用,我们将运行以下脚本,不过现在先不管它的输出。后面还会在一个新会话中再次运行这个脚本,查看受控环境中对内存使用的影响。我们会依次使用大小为64 KB、1 MB和1 GB的排序区:
create table t as select * from all_objects; exec dbms_stats.gather_table_stats( user, 'T' ); alter session set workarea_size_policy=manual; alter session set sort_area_size = 65536; set termout off select * from t order by 1, 2, 3, 4; set termout on alter session set sort_area_size=1048576; set termout off select * from t order by 1, 2, 3, 4; set termout on alter session set sort_area_size=1073741820; set termout off select * from t order by 1, 2, 3, 4; set termout on |
注意 数据库中处理SQL时,首先必须“解析”SQL语句,有两种类型的解析。第一种是硬解析(hard parse),数据库实例第一次解析查询时完成的就是硬解析,其中包括查询计划的生成和优化。第二种解析是软解析(soft parse),在此会跳过硬解析的许多步骤。由于对前面的查询完成了硬解析,后面再查询时就可以避免硬解析,所以在下面的操作中,我们不必测量与硬解析相关的开销(因为后面都只是软解析)。
现在,建议你注销刚才的SQL*Plus会话,紧接着再登录,这样能得到一个一致的环境;也就是说,相对于刚才的环境来讲,还没有做任何其他的工作。
为了确保使用手动内存管理,我们要专门设置,并指定一个很小的排序区大小(64 KB)。另外,还要标识会话ID(SID),以便监视该会话的内存使用情况。
ops$tkyte@ORA
Session altered. ops$tkyte@ORA
SID ---------- 151 |
下面需要在第二个单独的会话中测量第一个会话(SID 151)使用的内存。如果使用同一个会话测量自身的内存使用情况,在查询排序所用的内存时,这个查询本身可能会影响我们查看的结果。为了在第二个会话中测量内存,要使用我为此开发的一个SQL*Plus小脚本。实际上这是一对脚本,其中一个脚本名为reset_stat.sql,用于重置一个小表,并将一个SQL*Plus变量设置为SID,这个脚本如下:
drop table sess_stats;
create table sess_stats
( name varchar2(64), value number, diff number ); variable sid number exec :sid := &1 |
注意 使用这个脚本(或任何脚本)之前,先要确保你了解脚本会做什么。这个脚本会删除一个SESS_STATS表,然后重新创建。如果你的模式中已经有这样一个表,你可能得换个名字!
另一个脚本是watch_stat.sql,对于这个案例研究,脚本中使用了MERGE SQL语句,这样就能首先插入(INSERT)一个会话的统计值,以后再回过来对其进行更新,而无需单独的INSERT/UPDATE脚本:
merge into sess_stats using ( select a.name, b.value from v$statname a, v$sesstat b where a.statistic# = b.statistic# and b.sid = :sid and (a.name like '%ga %' or a.name like '%direct temp%') ) curr_stats on (sess_stats.name = curr_stats.name) when matched then update set diff = curr_stats.value - sess_stats.value, value = curr_stats.value when not matched then insert ( name, value, diff ) values ( curr_stats.name, curr_stats.value, null ) / select * from sess_stats order by name; |
这里我强调了“对于这个案例研究”,因为上面粗体显示的行(我们感兴趣的统计名)在不同的示例中会有所不同。在这个例子中,我们感兴趣的是名字里包括ga的统计结果(pga和uga),或者名字里有direct temp的统计结果(在Oracle
注意 在Oracle9i中,对临时空间的直接I/O不是这样表示的。我们要使用一个WHERE 子句,其中应包括and (a.name like '%ga %'or a.name like '%physical % direct%')。
从SQL*Plus命令行运行这个watch_stat.sql脚本时,可以看到会话的PGA和UGA内存统计信息列表,而且列出了对临时空间执行的I/O。在对会话151(也就是使用手动PGA内存管理的会话)做任何工作之前,下面使用以上脚本来看看这个会话当前使用了多少内存,以及对临时空间执行了多少次I/O:
ops$tkyte@ORA
6 rows merged.
NAME VALUE DIFF ------------------------------------------- ---------- ---------- physical reads direct temporary tablespace 0 physical writes direct temporary tablespace 0 session pga memory 498252 session pga memory max 498252 session uga memory 152176 session uga memory max 152176 |
可以看出,开始查询之前,UGA中大约有149 KB(152 176/1 024)的数据,PGA中大约有487 KB的数据。第一个问题是:“在PGA和UGA之间使用了多少内存?”也就是说,用了149 KB + 487 KB的内存吗?还是另外的某个数?这是一个很棘手的问题,除非你了解所监视的会话(SID为151)通过专用服务器还是共享服务器连接数据库,否则这个问题无法回答,而且就算你知道使用的是专用服务器连接还是共享服务器连接,可能也很难得出答案。如果采用专用服务器模式,UGA完全包含在PGA中,在这种情况下,进程或线程就使用487 KB的内存。如果使用共享服务器,UGA将从SGA中分配,PGA则在共享服务器中。所以,在共享服务器模式下,从前面的查询得到最后一行时[1],共享服务器进程可能会由其他人使用。这个PGA不再是“我们的” 了,所以,从技术上讲,我们使用了149 KB的内存(除非正在运行查询,此时还使用了PGA和UGA之间的487 KB内存)。下面在会话151中运行第一个大查询,这个会话采用专用服务器模式,并使用手动PGA内存管理。需要说明,这里还是使用前面的脚本,SQL文本完全一样,因此可以避免硬解析:
注意 由于我们还没有设置SORT_AREA_RETAINED_SIZE,所以报告的SORT_AREA_RETAINED_SIZE值将是0,但是排序区实际保留的大小等于SORT_AREA_SIZE。
ops$tkyte@ORA
Session altered.
ops$tkyte@ORA
query was executed here ops$tkyte@ORA
|
现在,如果在第二个会话中再次运行脚本,会得到下面的结果。注意,这一次session xxx memory和session xxx memory max值并不一样。session xxx memory值表示我们现在使用了多少内存。session xxx memory max值表示会话处理查询时某个时刻所使用内存的峰值。
ops$tkyte@ORA
6 rows merged.
NAME VALUE DIFF ------------------------------------------- ---------- ---------- physical reads direct temporary tablespace 2906 2906 physical writes direct temporary tablespace 2906 2906 session pga memory 498252 0 session pga memory max 563788 65536 session uga memory 152176 0 session uga memory max 217640 65464 6 rows selected. |
可以看到,使用的内存增加了,这里对数据做了某种排序。在处理查询期间,UGA临时从149 KB增加到213 KB(增加了64 KB),然后再收缩回原来的大小。这是因为,为了完成查询和排序,Oracle为会话分配了一个排序区。另外,PGA内存从487 KB增加到551 KB,增加了64 KB。另外可以看到,我们对临时空间执行了2 906次读和写。
如以上结果所示,完成查询并得到结果集之前,UGA内存又退回到原来的大小(从UGA释放了排序区),PGA也会有某种程度的收缩(注意,在Oracle8i 和以前的版本中,可能根本看不到PGA收缩;这是Oracle9i 及以上版本中新增的特性)。
下面再来完成这个操作,不过这一次SORT_AREA_SIZE增加到1 MB。注销所监视的会话,再登录,然后使用reset_stat.sql脚本从头开始。因为开始的统计结果都是一样的,所以这里不再显示,我只给出最后的结果:
ops$tkyte@ORA
Session altered.
ops$tkyte@ORA
query was executed here ops$tkyte@ORA
|
下面再在另一个会话中测量内存的使用情况:
ops$tkyte@ORA
6 rows merged.
NAME VALUE DIFF ------------------------------------------- ---------- ---------- physical reads direct temporary tablespace 684 684 physical writes direct temporary tablespace 684 684 session pga memory 498252 0 session pga memory max 2398796 1900544 session uga memory 152176 0 session uga memory max 1265064 1112888 6 rows selected. |
可以看到,这一次处理查询期间,PGA大幅增长。它临时增加了大约1 728 KB,但是进行数据排序所必须执行的物理I/O次数则显著下降(使用更多的内存,就会减少与磁盘的交换)。而且,我们还可以避免一种多趟排序(multipass sort),如果有太多很小的排序数据集合要合并(或归并),Oracle最后就要多次将数据写至临时空间,这种情况下就会发生多趟排序。现在,再来看一个极端情况:
ops$tkyte@ORA
Session altered.
ops$tkyte@ORA
query was executed here ops$tkyte@ORA
|
从另一个会话进行测量,可以看到迄今为止所使用的内存:
ops$tkyte@ORA
6 rows merged.
NAME VALUE DIFF ------------------------------------------- ---------- ---------- physical reads direct temporary tablespace 0 0 physical writes direct temporary tablespace 0 0 session pga memory 498252 0 session pga memory max 7445068 6946816 session uga memory 152176 0 session uga memory max 7091360 6939184 6 rows selected. |
可以观察到,尽管允许SORT_AREA_SIZE有1 GB,但实际上只用了大约6.6 MB。这说明SORT_AREA_SIZE设置只是一个上界,而不是默认的分配大小。还要注意,这里也做了排序,但是这一次完全在内存中进行;而没有使用磁盘上的临时空间,从物理I/O次数为0可以看出这一点。
如果在不同版本的Oracle上运行这个测试,甚至在不同操作系统上运行这个测试,都可能会看到不同的行为,相信你得到的数值肯定和我得到的结果稍有差异。但是一般的行为应该是一样的。换句话说,增加允许的排序区大小并完成大规模排序时,会话使用的内存量会增加。你可能注意到PGA内存上上下下地变化,或者可能一段时间总保持不变(前面已经介绍过这种情况)。例如,如果你在Oracle8i上执行前面的测试,肯定会注意到PGA内存大小根本没有收缩(也就是说,无论什么情况,SESSION PGA MEMORY都等于SESSION PGA MEMORY MAX)。这是可以想见的,因为在8i中,PGA作为堆来管理,并通过malloc()分配内存来创建。在9i和
在使用*_AREA_SIZE参数时,需要记住以下重要的几点:
q 这些参数控制着SORT、HASH和/或BITMAP MERGE操作所用的最大内存量。
q 一个查询可能有多个操作,这些操作可能都要使用这个内存,这样会创建多个排序/散列区。要记住,可以同时打开多个游标,每个游标都有自己的SORT_AREA_RETAINED需求。所以,如果把排序区大小设置为10 MB,在会话中实际上可以使用10 MB、100 MB、1 000 MB或更多RAM。这些设置并非对会话的限制;它们只是对一个操作的限制。你的会话中,一个查询可以有多个排序,或者多个查询需要一个排序。
q 这些内存区都是根据需要来分配的。如果像我们一样,将排序区大小设置为1 GB,这并不是说你要分配1 GB的RAM,而只是说,你允许Oracle进程为一个排序/散列操作最多分配1 GB的内存。
4.1.2 自动PGA内存管理
从Oracle9i Release 1起,又引入了一种新的方法来管理PGA内存,即自动PGA内存管理。这种方法中不再使用SORT_AREA_SIZE、BITMAP_MERGE_AREA_SIZE和HASH_AREA_SIZE这些参数。引入自动PGA内存管理是为了解决以下问题:
q 易用性:很多人并不清楚如何设置适当的*_AREA_SIZE参数。另外这些参数具体如何工作,内存究竟如何分配,这些问题都很让人困惑。
q 手动分配是一种“以一概全”的方法:一般地,随着在一个数据库上运行类似应用的用户数的增加,排序/散列所用的内存量也会线性增长。如果有10个并发用户,排序区大小为1 MB,这就会使用10 MB的内存,100个并发用户可能使用100 MB,1 000个并发用户则可能使用1 000 MB,依此类推。除非DBA一直坐在控制台前不断地调整排序/散列区大小设置,否则每个人每天可能都会使用同样的设置值。考虑一下前面的例子,你自己可以清楚地看到, 随着允许使用的RAM量的增加,对临时空间执行的物理I/O在减少。如果你自己运行这个例子,肯定会注意到,随着排序可用RAM的增加,响应时间会减少。手动分配会把排序所用的内存量固定为某个常量值,而不论实际有多少内存。利用自动内存管理的话,只有当内存真正可用时我们才会使用;自动内存管理会根据工作负载动态地调整实际使用的内存量。
q 内存控制:根据上一条,手动分配很难保证Oracle实例“合法”地使用内存,甚至不可能保证。你不能控制实例要用的内存量,因为你根本无从控制会发生多少并发的排序/散列。很有可能要使用的实际内存(真正的物理空闲内存)过多,而机器上并没有这么多可用内存。
下面来看自动PGA内存管理。首先简单地建立SGA并确定其大小。SGA是一段大小固定的内存,所以你可以准确地看到它有多大,这将是SGA的总大小(除非你改变了这个大小)。得到了SGA的大小后,再告诉Oracle:“你就要在这么多内存中分配所有工件区,所谓工作区(work area)只是排序区和散列区的另一种通用说法。”现在,理论上讲,如果一台机器有2 GB的物理内存,可以分配768 MB内存给SGA,768 MB内存分配给PGA,余下的512 MB内存留给操作系统和其他进程。我提到了“理论上”,这是因为实际情况不会毫厘不差,但是会很接近。为什么会这样呢?在做进一步的解释之前,先来看一下如何建立和打开自动PGA内存管理。
建立自动PGA内存管理时,需要为两个实例初始化参数确定适当的值,这两个参数是:
q WORKAREA_SIZE_POLICY:这个参数可以设置为MANUAL或AUTO,如果是MANUAL,会使用排序区和散列区大小参数来控制分配的内存量;如果是AUTO,分配的内存量会根据数据库中的当前工作负载而变化。默认值是AUTO,这也是推荐的设置。
q PGA_AGGREGATE_TARGET:这个参数会控制实例为完成数据排序/散列的所有工作区(即排序区和散列区)总共应分配多少内存。在不同的版本中,这个参数的默认值有所不同,可以用多种工具来设置,如DBCA。一般来讲,如果使用自动PGA内存管理,就应该显式地设置这个参数。
所以,假设WORKAREA_SIZE_POLICY设置为AUTO,PGA_AGGREGATE_TARGET有一个非0值,就会使用这种新引入的自动PGA内存管理。可以在会话中通过ALTER SESSION命令“打开”自动PGA内存管理,也可以在系统级通过ALTER SYSTEM命令[2]打开。
注意 要记住前面的警告,在Oracle9i中,共享服务器连接不会使用自动PGA内存管理;而是使用SORT_AREA_SIZE和HASH_AREA_SIZE参数来确定为各个操作分配多少RAM。在Oracle
所以,自动PGA内存管理的总目标就是尽可能充分地使用RAM,而且不会超出可用的RAM。倘若采用手动内存管理,这个目标几乎无法实现。如果将SORT_AREA_SIZE设置为10 MB,一个用户完成一个排序操作时,该用户会用掉排序工作区的10 MB内存。如果100个用户执行同样的操作,就会用掉1 000 MB的内存。如果你只有500 MB的空闲内存,那么无论对于单独的1个用户还是对于100个用户,这种设置都是不合适的。对于单独一个完成排序的用户来说,他本来可以使用更多的内存(而不只是10 MB),而对于100个想同时完成排序的用户来说,应该少用一些内存才行(应该少于10 MB)。自动PGA内存管理就是要解决这种问题。如果工作负载小,随着系统上负载的增加,会最大限度地使用内存;随着更多的用户完成排序或散列操作,分配给他们的内存量会减少,这样就能达到我们的目标,一方面使用所有可用的RAM,另一方面不要超额使用物理上不存在的内存。
1. 确定如何分配内存
有几个问题经常被问到:“内存是怎么分配的?”以及“我的会话使用了多少RAM?”这些问题都很难回答,原因只有一个,文档中没有说明采用自动模式时分配内存的算法,而且在不同版本中这个算法还可能(而且将会)改变。只要技术以A开头(表示自动,automatic),你就会丧失一定的控制权,而由底层算法确定做什么以及如何进行控制。
我们可以根据MetaLink 147806.1中的一些信息来做一些观察:
q PGA_AGGREGATE_TARGET是一个上限目标,而不是启动数据库时预分配的内存大小。可以把PGA_AGGREGATE_TARGET设置为一个超大的值(远远大于服务器上实际可用的物理内存量),你会看到,并不会因此分配很大的内存。
q 串行(非并行查询)会话会使用PGA_AGGREGATE_TARGET中的很少一部分,大约5%或者更少。所以,如果把PGA_AGGREGATE_TARGET设置为100 MB,可能每个工作区(例如,排序或散列工作区)只会使用大约不到5 MB。你的会话中可能为多个查询分配有多个工作区,或者一个查询中就有多个排序/散列操作,但是不论怎样,每个工作区只会用PGA_AGGREGATE_TARGET中不到5%的内存。
q 随着服务器上工作负载的增加(可能有更多的并发查询和更多的并发用户),分配给各个工作区的PGA内存量会减少。数据库会努力保证所有PGA分配的总和不超过PGA_AGGREGATE_TARGET设置的阈值。这就像有一位DBA整天坐在控制台前,不断地根据数据库中完成的工作量来设置SORT_AREA_SIZE和HASH_AREA_SIZE参数。稍后会通过一个测试来观察这种行为。
q 一个并行查询最多可以使用PGA_AGGREGATE_TARGET的30%,每个并行进程会在这30%中得到自己的那一份。也就是说,每个并行进程能使用的内存量大约是0.3*PGA_ AGGREGATE_TARGET / (并行进程数)。
那么,怎么观察分配给会话的不同工作区的大小呢?在介绍手动内存管理那一节中,我们介绍过一种技术,现在可以采用同样的技术来观察会话所用的内存以及对临时空间执行的I/O。以下测试在Red Hat Advanced Server 3.0 Linux主机上完成,使用了Oracle
merge into sess_stats using ( select a.name, b.value from v$statname a, v$sesstat b where a.statistic# = b.statistic# and b.sid = &1 and (a.name like '%ga %' or a.name like '%direct temp%') union all select 'total: ' || a.name, sum(b.value) from v$statname a, v$sesstat b, v$session c where a.statistic# = b.statistic# and (a.name like '%ga %' or a.name like '%direct temp%') and b.sid = c.sid and c.username is not null group by 'total: ' || a.name ) curr_stats on (sess_stats.name = curr_stats.name) when matched then update set diff = curr_stats.value - sess_stats.value, value = curr_stats.value when not matched then insert ( name, value, diff ) values ( curr_stats.name, curr_stats.value, null ) / |
除了单个会话的统计信息外,我只增加了UNION ALL部分,通过将所有会话的统计结果累加,从而得到总的PGA/UGA使用情况和排序写次数。然后在这个会话中运行以下SQL*Plus脚本。在此之前已经创建了BIG_TABLE表,而且填入了50 000行记录。我删除了这个表的主键,这样余下的只是表本身(从而确保肯定会执行一个排序过程):
set autotrace traceonly statistics; select * from big_table order by 1, 2, 3, 4; set autotrace off |
注意 BIG_TABLE表创建为ALL_OBJECTS的一个副本,并增加了主键,行数由你来定。big_table.sql 脚本在本书开头的“配置环境”一节中介绍过。
下面,对一个数据库运行这个小查询脚本,该数据库的PGA_AGGREGATE_TARGET设置为256 MB,这说明我希望Oracle使用最多约256 MB的PGA内存来完成排序。我还建立了另一个脚本,可以在其他会话中运行,它会在机器上生成很大的排序负载。这个脚本有一个循环,并使用一个内置的包(DBMS_ALERT)查看是否继续处理。如果是,则再一次运行这个大查询,对整个BIG_TABLE表排序。仿真结束后,再由一个会话通知所有排序进程(也就是负载生成器)“停止”并退出。执行排序的脚本如下:
declare l_msg long; l_status number; begin dbms_alert.register( 'WAITING' ); for i in 1 .. 999999 loop dbms_application_info.set_client_info( i ); dbms_alert.waitone( 'WAITING', l_msg, l_status, 0 ); exit when l_status = 0; for x in ( select * from big_table order by 1, 2, 3, 4 ) loop null; end loop; end loop; end; / exit |
以下脚本会让这些进程停止运行:
begin dbms_alert.signal( 'WAITING', '' ); commit; end; |
为了观察对所测量的会话分配的RAM量有什么不同,首先独立地运行SELECT查询,这样就只有一个会话。我得到了与前面相同的6个统计结果,并把这些统计结果连同活动会话数[3]保存在另一个表中。然后,再向系统增加25个会话(也就是说,在25个新会话中运行以上带循环的基准测试脚本)。接下来等待很短时间(1分钟),让系统能针对这个新负载做出调整。然后我创建了一个新会话,用reset_stat.sql得到其统计结果,再运行执行排序的查询,接下来运行 watch_stat.sql得到排序前后的统计结果之差。然后重复地做了这个工作,直至并发用户数达到500。
需要说明,这里实际上在要求数据库实例做它根本做不了的事情。前面已经提到,第一次运行watch_stat.sql时,每个Oracle连接在完成排序之前会使用大约0.5 MB的RAM。如果有500个并发用户全部登录,单单是他们登录所用的内存就已经非常接近所设置的PGA_AGGREGATE_TARGET(PGA_AGGREGATE_TARGET设置为256 MB),更不用说具体做工作了!由此再一次表明,PGA_AGGREGATE_TARGET只是一个目标,而不是明确地指定要分配多少空间。出于很多原因,实际分配的空间还可能超过这个值。
表4-1总结了每次增加大约25个用户时得到的统计结果。
表4-1 随着活动会话数的增加,PGA内存分配行为的变化(PGA_AGGREGATE_TARGET设置为256 MB)
活动会话 一个会话使用的PGA 系统使用的PGA 一个会话的临时写 一个会话的临时读
1 7.5 2 0 0
27 7.5 189 0 0
51 4.0 330 728 728
76 4.0 341 728 728
101 3.2 266 728 728
126 1.5 214 728 728
151 1.7 226 728 728
177 1.4 213 728 728
201 1.3 218 728 728
226 1.3 211 728 728
251 1.3 237 728 728
276 1.3 251 728 728
301 1.3 281 728 728
326 1.3 302 728 728
351 1.3 324 728 728
376 1.3 350 728 728
402 1.3 367 728 728
426 1.3 392 728 728
452 1.3 417 728 728
476 1.3 439 728 728
501 1.3 467 728 728
注意 你可能会奇怪,有1个活动用户时,为什么系统使用的RAM只报告为2 MB。这与我的测量方法有关。这个仿真会对所测会话中的统计结果建立快照。接下来,我会在所测的这个会话中运行上述大查询,然后再次记录该会话统计结果的快照。最后再来测量系统使用了多少PGA。在我测量所用的PGA时,这个会话已经结束,并交回了它用于排序的一些PGA。所以,这里系统使用的PGA只是对测量时系统所用PGA内存的准确度量。
可以看到,如果活动会话不多,排序则完全在内存中执行。活动会话数在1~50之间时,我就可以完全在内存中执行排序。不过,等到有50个用户登录并执行排序时,数据库就会开始控制一次能使用的内存量。所用的PGA量要退回到可接受的限值(256 MB)以内,在此之前需要几分钟的时间来调整,不过最后总是会落回到阈值范围内。分配给会话的PGA内存量从7.5 MB降到4 MB,随后又降到3.2 MB,最后降至1.7~1.3 MB之间(要记住,PGA中有一部分不用于排序,也不用于其他操作,光是登录这个动作就要创建0.5 MB的PGA)。系统使用的总PGA量仍保持在可以接受的限值内,直至用户数达到300~351之间。从这里开始,系统使用的PGA开始有规律地超过PGA_AGGREGATE_TARGET,并延续至测试结束。在这个例子中,我交给数据库实例一个不可能完成的任务,光是支持350个用户(大多数都执行一个PL/SQL,再加上他们都要请求排序),我设定的这个目标(256 MB的RAM)就无法胜任。每个会话只能使用尽可能少的内存,但另一方面又必须分配所需的足够内存,所以这根本就办不到。等我完成这个测试时,500个活动会话已经使用了总共467 MB的PGA内存,大大超出了我所设定的目标(256 MB),但对每个会话来说使用的内存已经够少的了。
不过,再想想在手动内存管理情况下表4-1会是什么样子。假设SORT_AREA_SIZE设置为5 MB。计算很简单:每个会话都能在RAM中执行排序(如果实际RAM用完了,还可以使用虚拟内存),这样每个会话会使用6~7 MB的RAM(与前面只有一个用户而且不在磁盘上排序时使用的内存量相当)。再运行前面的测试,将SORT_AREA_SIZE设置为5 MB,从1个用户开始,每次增加25个用户,得到的结果保持一致,如表4-2所示。
表4-2 随着活动会话数的增加,PGA内存分配行为的变化(手动内存管理,SORT_AREA_SIZE设置为5 MB)
活动会话 一个会话使用的PGA 系统使用的PGA 一个会话的临时写 一个会话的临时读
1 6.4 5 728 728
26 6.4 137 728 728
51 6.4 283 728 728
76 6.4 391 728 728
102 6.4 574 728 728
126 6.4 674 728 728
151 6.4 758 728 728
176 6.4 987 728 728
202 6.4 995 728 728
226 6.4 1227 728 728
251 6.4 1383 728 728
277 6.4 1475 728 728
302 6.4 1548 728 728
如果我能完成这个测试(这个服务器上有2 GB的实际内存,我的SGA是600 MB;等到用户数达到325时,机器换页和交换开始过于频繁,而无法继续工作),有500个并发用户时,我就要分配大约2 750 MB的RAM!所以,在这个系统上DBA可能不会将SORT_AREA_SIZE设置为5 MB,而是设置为0.5 MB,力图使高峰期的最大PGA使用量在可以忍受的范围内。现在如果并发用户数为500,就要分配大约500 MB的PGA,这可能与采用自动内存管理时所观察到的结果类似,但是即使用户不太多,还是会写临时空间,而不是在内存中执行排序。实际上,如果SORT_AREA_SIZE设置为0.5 MB,再运行以上测试,会观察到表4-3所示的数据。
表4-3 随着活动会话数的增加,PGA内存分配行为的变化(手动内存管理,SORT_AREA_SIZE设置为0.5 MB)
活动会话 一个会话使用的PGA 系统使用的PGA 一个会话的临时写 一个会话的临时读
1 1.2 1 728 728
26 1.2 29 728 728
51 1.2 57 728 728
76 1.2 84 728 728
101 1.2 112 728 728
126 1.2 140 728 728
151 1.2 167 728 728
176 1.2 194 728 728
201 1.2 222 728 728
226 1.2 250 728 728
工作负载随着时间的推移而增加或减少时,这种内存的使用完全可以预计,但是并不理想。自动PGA内存管理正是为此设计的。在有足够的内存时,自动内存管理会让少量的用户尽可能多地使用RAM,而过一段时间负载增加时,可以减少分配,再过一段时间,随着负载的减少,为每个操作分配的RAM量又能增加。
2. 使用PGA_AGGREGATE_TARGET控制内存分配
之前我曾说过,“理论上”可以使用PGA_AGGREGATE_TARGET来控制实例使用的PGA内存的总量。不过,从上一个例子中可以看到,这并不是一个硬性限制。实例会尽力保持在PGA_AGGREGATE_TARGET限制以内,但是如果实在无法保证,它也不会停止处理;只是要求超过这个阈值。
这个限制只是一个“理论上”的限制,对此还有一个原因:尽管工作区在PGA内存中所占的比重很大,但PGA内存中并非只有工作区。PGA内存分配涉及很多方面,其中只有工作区在数据库实例的控制之下。如果创建并执行一个PL/SQL代码块将数据填入一个很大的数组,这里采用专用服务器模式,因此UGA在PGA中,倘若是这样,Oracle只能任由你这样做,而无法干涉。
考虑下面这个小例子。我们将创建一个包,其中可以保存服务器中的一些持久(全局)数据:
ops$tkyte@ORA
2 as 3 type array is table of char(2000) index by binary_integer;
5 end; 6 / Package created. |
下面,测量这个会话当前使用的PGA/UGA内存量(这个例子使用了专用服务器,所以UGA在PGA内存中,是PGA的一个子集):
ops$tkyte@ORA
2 from v$statname a, v$mystat b 3 where a.statistic# = b.statistic# 4 and a.name like '%ga memory%';
NAME VALUE ------------------------------ ------------ session uga memory 1,212,872 session uga memory max 1,212,872 session pga memory 1,677,900 session pga memory max 1,677,900 |
所以,最初会话中使用了大约1.5 MB的PGA内存(因为还要编译 PL/SQL包,运行这个查询,等等)。现在,再对BIG_TABLE运行这个查询,这里PGA_AGGREGATE_TARGET同样是256 MB(这一回是在一个空闲的实例上执行查询;现在我们是惟一需要内存的会话):
ops$tkyte@ORA10GR1> set autotrace traceonly statistics; ops$tkyte@ORA10GR1> select * from big_table order by 1,2,3,4; 50000 rows selected.
Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 721 consistent gets 0 physical reads 0 redo size 2644246 bytes sent via SQL*Net to client 37171 bytes received via SQL*Net from client 3335 SQL*Net roundtrips to/from client 1 sorts (memory) 0 sorts (disk) 50000 rows processed ops$tkyte@ORA10GR1> set autotrace off |
可以看到,排序完全在内存中完成,实际上,如果看一下这个会话的PGA/UGA使用情况,就能看出我们用了多少PGA/UGA内存:
ops$tkyte@ORA10GR1> select a.name, to_char(b.value, '999,999,999') value 2 from v$statname a, v$mystat b 3 where a.statistic# = b.statistic# 4 and a.name like '%ga memory%'; NAME VALUE ------------------------------ ------------ session uga memory 1,212,872 session uga memory max 7,418,680 session pga memory 1,612,364 session pga memory max 7,838,284 |
还是前面观察到的7.5 MB的RAM。现在,再填入包中的CHAR数组(CHAR数据类型用空格填充,这样每个数组元素的长度都正好是2 000个字符):
ops$tkyte@ORA
2 for i in 1 .. 100000 3 loop 4 demo_pkg.g_data(i) := 'x'; 5 end loop; 6 end; 7 / PL/SQL procedure successfully completed. |
在此之后,测量会话当前使用的PGA,可以看到下面的结果:
ops$tkyte@ORA10GR1> select a.name, to_char(b.value, '999,999,999') value 2 from v$statname a, v$mystat b 3 where a.statistic# = b.statistic# 4 and a.name like '%ga memory%';
NAME VALUE ------------------------------ ------------ session uga memory 312,952,440 session uga memory max 312,952,440 session pga memory 313,694,796 session pga memory max 313,694,796 |
现在,数据库本身无法控制PGA中分配的这些内存。我们已经超过了PGA_AGGREGATE_TARGET,但数据库对此无计可施,如果它能干预的话,肯定会拒绝我们的请求,不过只有当操作系统报告称再没有更多可用内存时我们的请求才会失败。如果想试试看,你可以在数组中分配更多的空间,再向其中放入更多的数据,等到操作系统报告再无内存可用时,数据库就会“忍无可忍”地拒绝内存分配请求。
不过,数据库很清楚我们做了什么。尽管有些内存无法控制,但它不会忽略这部分内存;而是会识别已经使用的内存,并相应地减少为工作区分配的内存大小。所以,如果再运行同样的排序查询,可以看到,这一次会在磁盘上排序,如果要在内存中排序,这需要7 MB左右的RAM,但是数据库没有提供这么多RAM,原因是分配的内存已经超过了PGA_AGGREGATE_TARGET:
ops$tkyte@ORA10GR1> set autotrace traceonly statistics; ops$tkyte@ORA10GR1> select * from big_table order by 1,2,3,4; 50000 rows selected.
Statistics ---------------------------------------------------------- 6 recursive calls 2 db block gets 721 consistent gets 728 physical reads 0 redo size 2644246 bytes sent via SQL*Net to client 37171 bytes received via SQL*Net from client 3335 SQL*Net roundtrips to/from client 0 sorts (memory) 1 sorts (disk) 50000 rows processed ops$tkyte@ORA10GR1> set autotrace off |
因此,由于一些PGA内存不在Oracle的控制之下,所以如果在PL/SQL代码中分配了大量很大的数据结构,就很容易超出PGA_AGGREGATE_TARGET。在此并不是建议你绝对不要这样做,我只是想说明PGA_AGGREGATE_TARGET不能算是一个硬性限制,而更应该算是一个请求。
4.1.3 手动和自动内存管理的选择
那么,你要用哪种方法呢?手动还是自动?默认情况下,我倾向于自动PGA内存管理。
警告 在这本书里我一而再、再而三地提醒你:不要对生产系统(实际系统)做任何修改,除非先测试修改有没有副作用。例如,先别阅读这一章,检查你的系统,看看是不是在使用手动内存管理,然后再打开自动内存管理。查询计划可能改变,而且也许还会影响性能。可能会发生以下3种情况之一:
q 运行得完全一样。
q 比以前运行得好。
q 比以前运行得差。
在做出修改之前一定要谨慎,应当先对要做的修改进行测试。
最让DBA头疼的一件事可能就是设置各个参数,特别是像SORT|HASH_AREA_SIZE之类的参数。系统运行时这些参数的值可能设置得相当小,而且实在是太小了,以至于性能受到了负面影响,这种情况我已经屡见不鲜。造成这种情况的原因可能是默认值本身就非常小:排序区的默认大小为64 KB,散列区的默认大小也只是128 KB。这些值应该多大才合适呢?对此人们总是很困惑。不仅如此,在一天中的不同时段,你可能还想使用不同的值。早上8:00只有两个用户,此时登录的每个用户使用50 MB大小的排序区可能很合适。不过,到中午12:00,已经有500个用户,再让每个用户使用50 MB的排序区就不合适了。针对这种情况,WORKAREA_SIZE_POLICY = AUTO和相应的PGA_AGGREGATE_TARGET就能派上用场了。要设置PGA_AGGREGATE_TARGET,也就是你希望Oracle能*使用多大的内存来完成排序和散列,从概念上讲这比得出最佳的SORT|HASH_AREA_SIZE要容易得多,特别是,SORT|HASH_AREA_SIZE之类的参数并没有一个最佳的值;“最佳值”会随工作负载而变化。
从历史上看,DBA都是通过设置SGA(缓冲区缓存、日志缓冲区、共享池、大池和Java池)的大小来配置Oracle使用的内存量。机器上余下的内存则由PGA区中的专用或共享服务器使用。对于会使用(或不会使用)其中的多少内存,DBA无从控制。不错,DBA确实能设置SORT_AREA_SIZE,但是如果有10个并发的排序,Oracle就会使用10 * SORT_AREA_SIZE字节的RAM。如果有100个并发的排序,Oracle将使用100 * SORT_AREA_SIZE字节;倘若是1 000个并发的排序,则会使用1 000 *SORT_AREA_SIZE;依此类推。不仅如此,再加上PGA中还有其他内容,你根本不能很好地控制系统上最多能使用的PGA内存量。
你所希望的可能是:随着系统上内存需求的增加和减少,会使用不同大小的内存。用户越多,每个用户使用的RAM就越少。用户越少,每个用户能使用的RAM则越多。设置WORKAREA_SIZE_POLICY = AUTO就是要达到这个目的。现在DBA只指定一个大小值,即PGA_AGGREGATE_TARGET,也就是数据库应当努力使用的最大PGA内存量。Oracle会根据情况将这个内存适当地分配给活动会话。另外,在Oracle9i Release 2及以上版本中,甚至还有一个PGA顾问(PGA advisory),这是Statspack的一部分,可以通过一个V$动态性能视图得到,也可以在企业管理器(EM)中看到,PGA顾问与缓冲区缓存顾问很相似。它会一直告诉你,为了尽量减少对临时表空间执行的物理I/O,系统最优的PGA_AGGREGATE_TARGET是什么。可以使用这个信息动态修改PGA大小(如果你有足够多的RAM),或者确定是否需要服务器上的更多RAM来得到最优性能。
不过,有没有可能不想使用自动PGA内存管理的情况呢?当然有,好在不想用的情况只是例外,而不是一般现象。自动内存管理力图对多个用户做到“公平”。由于预见到可能有另外的用户加入系统,因此自动内存管理会限制分配的内存量只是PGA_AGGREGATE_TARGET的一部分。但是假如你不想要公平,确实知道应该得到所有可用的内存(而不是其中的一部分),这该怎么办?倘若如此,就应该使用ALTER SESSION命令在你的会话中禁用自动内存管理(而不影响其他会话),并且根据需要,手动地设置你的SORT|HASH_AREA_SIZE。例如,凌晨2:00要做一个大型的批处理,它要完成大规模的散列联结、建立索引等工作,对于这样一个批处理作业,你要怎么做?它应该可以使用机器上的所有资源[5]。在内存使用方面,它不想“公平”,而是全部都想要,因为它知道,现在数据库中除了它以外再没有别的任务了。当然这个批处理作业可以发出ALTER SESSION命令,充分使用所有可用的资源。
所以,简单地讲,对于成天在数据库上运行的应用,我倾向于对最终用户会话使用自动PGA内存管理。手动内存管理则适用于大型批处理作业(它们在特殊的时段运行,此时它们是数据库中惟一的活动)。
4.1.4 PGA和UGA小结
以上讨论了两种内存结构:PGA和UGA。你现在应该了解到,PGA是进程专用的内存区。这是Oracle专用或共享服务器需要的一组独立于会话的变量。PGA是一个内存“堆”,其中还可以分配其他结构。UGA也是一个内存堆,其中定义不同会话特有的结构。如果使用专用服务器来连接Oracle,UGA会从PGA分配,如果使用共享服务器连接,UGA则从SGA分配。这说明,使用共享服务器时,必须适当地设置SGA中大池(large pool)的大小,以便有足够的空间来适应可能并发地连接数据库的每一个用户。所以,如果数据库支持共享服务器连接,与有类似配置但只使用专用服务器模式的数据库相比,前者的SGA通常比后者大得多。下面将更详细地讨论SGA。
4.2 系统全局区
每个Oracle实例都有一个很大的内存结构,称为系统全局区(System Global Area,SGA)。这是一个庞大的共享内存结构,每个Oracle进程都会访问其中的某一点。SGA的大小不一,在小的测试系统上只有几MB,在中到大型系统上可能有几百MB,对于非常大的系统,甚至多达几GB。
在UNIX操作系统上,SGA是一个物理实体,从操作系统命令行上能“看到”它。它物理地实现为一个共享内存段,进程可以附加到这段独立的内存上。系统上也可以只有SGA而没有任何Oracle进程;只有内存而已。不过,需要说明,如果有一个SGA而没有任何Oracle进程,这就说明数据库以某种方式崩溃了。这是一种很罕见的情况,但是确实有可能发生。以下是Red Hat Linux上SGA的“样子”:
[tkyte@localhost tkyte]$ ipcs -m | grep ora 0x99875060 2031619 ora
0x0d
0x6b390abc 1998857 ora9ir1 660 130560000 50 |
这里表示了3个SGA:一个属于操作系统用户ora
在Windows上,则无法像UNIX/Linux上那样把SGA看作一个实体。由于在Windows平台上, Oracle会作为有一个地址空间的单个进程来执行,所以SGA将作为专用(私有)内存分配给oracle.exe进程。如果使用Windows Task Manager(任务管理器)或其他性能工具,则可以看到oracle.exe总共分配了多少空间,但是SGA和其他已分配的内存无法看到。
在Oracle自身内,则完全可以看到SGA,而不论平台是什么。为此,只需使用另一个神奇的V$视图,名为V$SGASTAT。它可能如下所示(注意,这个代码并非来自前面的系统;而是来自一个已经适当地配置了相应特性的系统,从而可以查看所有可用的池):
ops$tkyte@ORA
ops$tkyte@ORA
ops$tkyte@ORA
2 from v$sgastat 3 order by pool, name;
POOL NAME BYTES ------------ ------------------------------ ---------- java pool free memory 16777216 ************ ---------- sum 16777216 large pool PX msg pool 64000 free memory 16713216 ************ ---------- sum 16777216 shared pool ASH buffers 2097152 FileOpenBlock 746704 KGLS heap 777516 KQR L SO 29696 KQR M PO 599576 KQR M SO 42496 ... sql area 2664728 table definiti 280 trigger defini 1792 trigger inform 1944 trigger source 640 type object de 183804 ************ ---------- sum 352321536 streams pool free memory 33554432 ************ ---------- sum 33554432 buffer_cache 1157627904 fixed_sga 779316 log_buffer 262144 ************ ---------- sum 1158669364 43 rows selected. |
SGA分为不同的池(pool):
q
Java池(Java pool):Java池是为数据库中运行的JVM分配的一段固定大小的内存。在Oracle
q
大池(Large pool):共享服务器连接使用大池作为会话内存,并行执行特性使用大池作为消息缓冲区,另外RMAN备份可能使用大池作为磁盘I/O缓冲区。在Oracle
q
共享池(Shared pool):共享池包含共享游标(cursor)、存储过程、状态对象、字典缓存和诸如此类的大量其他数据。在Oracle
q
流池(Stream pool):这是Oracle流(Stream)专用的一个内存池,Oracle流是数据库中的一个数据共享工具。这个工具是Oracle
q “空”池(“Null”pool):这个池其实没有名字。这是块缓冲区(缓存的数据库块)、重做日志缓冲区和“固定SGA”区专用的内存。
典型的SGA可能如图4-1所示。
图4-1 典型的SGA
对SGA整体大小影响最大的参数如下:
q JAVA_POOL_SIZE:控制Java池的大小。
q SHARED_POOL_SIZE:在某种程度上控制共享池的大小。
q LARGE_POOL_SIZE:控制大池的大小。
q DB_*_CACHE_SIZE:共有8个CACHE_SIZE参数,控制各个可用的缓冲区缓存的大小。
q LOG_BUFFER:在某种程度上控制重做缓冲区的大小。
q
SGA_TARGET:Oracle
q SGA_MAX_SIZE:用于控制数据库启动并运行时SGA可以达到的最大大小。
在Oracle9i中,各个SGA组件必须由DBA手动地设置大小,但是从Oracle
不论是使用自动内存管理还是手动内存管理,都会发现各个池的内存以一种称为颗粒(granule,也称区组)的单位来分配。一个颗粒是大小为4 MB、8 MB或16 MB的内存区。颗粒是最小的分配单位,所以如果想要一个5 MB的Java池,而且颗粒大小为4 MB,Oracle实际上会为这个Java池分配8 MB(在4的倍数中,8是大于或等于5的最小的数)。颗粒的大小由SGA的大小确定(听上去好像又转回来了,因为SGA的大小取决于颗粒的大小)。通过查询V$SGA_DYNAMIC_ COMPONENTS,可以查看各个池所用的颗粒大小。实际上,还可以使用这个视图来查看SGA的总大小如何影响颗粒的大小:
sys@ORA
NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ sga_target big integer
sys@ORA
COMPONENT GRANULE_SIZE ------------------------- ------------ shared pool 4194304 large pool 4194304 java pool 4194304 streams pool 4194304 DEFAULT buffer cache 4194304 KEEP buffer cache 4194304 RECYCLE buffer cache 4194304 DEFAULT 2K buffer cache 4194304 DEFAULT 4K buffer cache 4194304 DEFAULT 8K buffer cache 4194304 DEFAULT 16K buffer cache 4194304 DEFAULT 32K buffer cache 4194304 OSM Buffer Cache 4194304 13 rows selected. |
在这个例子中,我使用了自动SGA内存管理,并通过一个参数(SGA_TARGET)来控制SGA的大小。SGA小于1 GB时,颗粒为4 MB。当SGA大小增加到超过阈值1 GB时(对于不同的操作系统,甚至对于不同的版本,这个阈值可能稍有变化),可以看到颗粒大小有所增加:
sys@ORA
System altered.
sys@ORA
ORACLE instance started.
Total System Global Area 1593835520 bytes Fixed Size 779316 bytes Variable Size 401611724 bytes Database Buffers 1191182336 bytes Redo Buffers 262144 bytes Database mounted. Database opened. sys@ORA
COMPONENT GRANULE_SIZE ------------------------- ------------ shared pool 16777216 large pool 16777216 java pool 16777216 streams pool 16777216 DEFAULT buffer cache 16777216 KEEP buffer cache 16777216 RECYCLE buffer cache 16777216 DEFAULT 2K buffer cache 16777216 DEFAULT 4K buffer cache 16777216 DEFAULT 8K buffer cache 16777216 DEFAULT 16K buffer cache 16777216 DEFAULT 32K buffer cache 16777216 OSM Buffer Cache 16777216 13 rows selected. |
可以看到,SGA为1.5 GB时,会以16 MB的颗粒为池分配空间,所以池大小都将是16 MB的某个倍数。
记住这一点,下面逐一分析各个主要的SGA组件。
4.2.1 固定SGA
固定SGA(fixed SGA)是SGA的一个组件,其大小因平台和版本而异。安装时,固定SGA会“编译到”Oracle二进制可执行文件本身当中(所以它的名字里有“固定”一词)。在固定SGA中,有一组指向SGA中其他组件的变量,还有一些变量中包含了各个参数的值。我们无法控制固定SGA的大小,不过固定SGA通常都很小。可以把这个区想成是 SGA中的“自启”区,Oracle在内部要使用这个区来找到SGA的其他区。
4.2.2 重做缓冲区
如果数据需要写到在线重做日志中,则在写至磁盘之前要在重做缓冲区(redo buffer)中临时缓存这些数据。由于内存到内存的传输比内存到磁盘的传输快得多,因此使用重做日志缓冲区可以加快数据库的操作。数据在重做缓冲区里停留的时间不会太长。实际上,LGWR会在以下某个情况发生时启动对这个区的刷新输出(flush):
q 每3秒一次
q 无论何时有人提交请求
q 要求LGWR切换日志文件
q 重做缓冲区1/3满,或者包含了1 MB的缓存重做日志数据
由于这些原因,如果重做缓冲区的大小超过几MB,通常对系统就没有什么意义了,实际上,能从这么大的重做缓冲区得到好处的系统极为少见。如果是一个有大量并发事务的大型系统,也许大的重做日志缓冲区会对它有利,因为LGWR(这个进程负责将重做日志缓冲区刷新输出到磁盘)写日志缓冲区的一部分时,其他会话可能会在缓冲区中填入新的数据。一般而言,如果一个事务长时间运行,就会生成大量重做日志,倘若采用更大的日志缓冲区而不是正常的日志缓冲区,对这种事务最有好处。因为在LGWR忙于将部分重做日志写出到磁盘时,重做日志缓冲区还会继续填入日志。事务越大、越长,大日志缓冲区的好处就越显著。
重做缓冲区的默认大小由LOG_BUFFER参数控制,取值为512 KB和(128 * CPU个数)KB中的较大者。这个区的最小大小取决于操作系统。如果想知道到底是多少,只需将LOG_BUFFER设置为1字节,再重启数据库。例如,在我的 Red Hat Linux实例上,可以看到以下输出:
sys@ORA
System altered.
sys@ORA
ORACLE instance started.
Total System Global Area 1593835520 bytes Fixed Size 779316 bytes Variable Size 401611724 bytes Database Buffers 1191182336 bytes Redo Buffers 262144 bytes Database mounted. Database opened. sys@ORA
NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ log_buffer integer 262144 |
在这个系统上,可能的最小日志缓冲区就是256 KB(而不论我设置的是多少)。
4.2.3 块缓冲区缓存
到目前为止,我们已经介绍了SGA中一些相对较小的组件。下面再来看看可能比较大的组件。Oracle将数据库块写至磁盘之前,另外从磁盘读取数据库块之后,就会把这些数据库块存储在块缓冲区缓存(block buffer cache)中。对我们来说,这是SGA中一个很重要的区。如果太小,我们的查询就会永远也运行不完。如果太大,又会让其他进程饥饿(例如,没有为专用服务器留下足够的空间来创建其PGA,甚至无法启动)。
在Oracle的较早版本中,只有一个块缓冲区缓存,所有段的所有块都放在这个区中。从Oracle 8.0开始,可以把SGA中各个段的已缓存块放在3个位置上:
q 默认池(default pool):所有段块一般都在这个池中缓存。这就是原先的缓冲区池(原来也只有一个缓冲区池)。
q 保持池(keep pool):按惯例,访问相当频繁的段会放在这个候选的缓冲区池中,如果把这些段放在默认缓冲区池中,尽管会频繁访问,但仍有可能因为其他段需要空间而老化(aging)。
q 回收池(recycle pool):按惯例,访问很随机的大段可以放在这个候选的缓冲区池中,这些块会导致过量的缓冲区刷新输出,而且不会带来任何好处,因为等你想要再用这个块时,它可能已经老化退出了缓存。要把这些段与默认池和保持池中的段分开,这样就不会导致默认池和保持池中的块老化而退出缓存。
需要注意,在保持池和回收池的描述中,我用了一个说法“按惯例”。因为你完全有可能不按上面描述的方式使用保持池或回收池,这是无法保证的。实际上,这3个池会以大体相同的方式管理块;将块老化或缓存的算法并没有根本的差异。这样做的目标是让DBA能把段聚集到“热”区(hot)、“温”区(warm)和“不适合缓存”区(do not care to cache)。理论上讲,默认池中的对象应该足够热(也就是说,用得足够多),可以保证一直呆在缓存中。缓存会把它们一直留在内存中,因为它们是非常热门的块。可能还有一些段相当热门,但是并不太热;这些块就作为温块。这些段的块可以从缓存刷新输出,为不常用的一些块(“不适合缓存”块)腾出空间。为了保持这些温段的块得到缓存,可以采取下面的某种做法:
q 将这些段分配到保持池,力图让温块在缓冲区缓存中停留得更久。
q 将“不适合缓存”段分配到回收池,让回收池相当小,以便块能快速地进入缓存和离开缓存(减少管理的开销)。
这样会增加DBA所要执行的管理工作,因为要考虑3个缓存,要确定它们的大小,还要为这些缓存分配对象。还要记住,这些池之间没有共享,所以,如果保持池有大量未用的空间,即使默认池或回收池空间不够用了,保持池也不会把未用空间交出来。总之,这些池一般被视为一种非常精细的低级调优设备,只有所有其他调优手段大多用过之后才应考虑使用(如果可以重写查询,将I/O减少为原来的1/10,而不是建立多个缓冲区池,我肯定会选择前者!)。
从Oracle9i开始,除了默认池、保持池和回收池外,DBA还要考虑第4种可选的缓存:db_Nk_caches。增加这些缓存是为了支持数据库中多种不同的块大小。在Oracle9i之前,数据库中只有一种块大小(一般是2 KB、4 KB、8 KB、16 KB或32 KB)。从Oracle9i开始,数据库可以有一个默认的块大小,也就是默认池、保持池或回收池中存储的块的大小,还可以有最多4种非默认的块大小,请见第3章的解释。
与原来默认池中的块一样,这些缓冲区缓存中的块会以同样的方式管理,没有针对不同的池采用任何特殊的算法。下面来看在这些池中如何管理块。
1. 在缓冲区缓存中管理块
为简单起见,这里假设只有一个默认池。由于其他池都以同样的方式管理,所以我们只需要讨论其中一个池。
缓冲区缓存中的块实质上在一个位置上管理,但有两个不同的列表指向这些块:
q 脏(dirty)块列表,其中的块需要由数据库块写入器(DBWn;稍后将介绍这个进程)写入磁盘。
q 非脏(nondirty)块列表。
在Oracle 8.0及以前版本中,非脏块列表就是最近最少使用(Least Recently Used,LRU)列表。块按使用的顺序列出。在Oracle8i及以后版本中,算法稍有修改。不再按物理顺序来维护块列表,Oracle采用了一种接触计数(touch count,也称使用计数)算法,如果命中缓存中的一个块,则会增加与之相关联的计数器。不是说每次命中这个块都会增加计数,而是大约每3秒一次(如果你连续命中的话)。有一组相当神奇的X$表,利用其中的某个表就可以看出这个算法是怎样工作的。在Oracle的文档中完全没有提到X$表,但是有关的信息还是时不时地会漏出来一些。
X$BH表显示了块缓冲区缓存中块的有关信息(文档中有记录的V$BH视图也能提供块的有关信息,不过X$BH表提供的信息更多)。在这个表中可以看到,我们命中块时,接触计数会增加。可以对这个表运行以下查询,得到5个“当前最热的块”,并把这个信息与DBA_OBJECTS视图联结,得出这些块属于哪些段。这个查询按TCH(接触计数)列对X$BH中的行排序,并保留前5行。然后按X$BH.OBJ等于DBA_OBJECTS.DATA_OBJECT_ID为条件将X$BH信息与DBA_OBJECTS联结:
sys@ORA
2 case when obj = 4294967295 3 then 'rbs/compat segment' 4 else (select max( '('||object_type||') ' || 5 owner || '.' || object_name ) || 6 decode( count(*), 1, '', ' maybe!' ) 7 from dba_objects 8 where data_object_id = X.OBJ ) 9 end what 10 from ( 11 select tch, file#, dbablk, obj 12 from x$bh 13 where state <> 0 14 order by tch desc 15 ) x 16 where rownum <= 5 17 / TCH FILE# DBABLK WHAT ---------- ---------- ---------- ---------------------------------------- 51099 1 1434 (TABLE) SYS.JOB$ 49780 1 1433 (TABLE) SYS.JOB$ 48526 1 1450 (INDEX) SYS.I_JOB_NEXT 11632 2 57 rbs/compat segment 10241 1 1442 (INDEX) SYS.I_JOB_JOB |
注意 (2^32 – 1) 或4 294 967 295是一个神奇的数,常用来指示“特殊”的块。如果想了解块关联的信息,可以使用查询select * from dba_extents where file_id = FILE# and block_id <= <DBABLK and block_id+blocks-1 >= DBABLK。
你可能会问,'maybe!'是什么意思,前面的标量子查询中为什么使用MAX()。这是因为,DATA_OBJECT_ID不是DBA_OBJECTS视图中的“主键”,通过下面的例子可以说明这一点:
sys@ORA
2 from dba_objects 3 where data_object_id is not null 4 group by data_object_id 5 having count(*) > 1;
DATA_OBJECT_ID COUNT(*) ------------------------- -------------- 2 17 6 3 8 3 10 3 29 3 161 3 200 3 210 2 294 7 559 2 10 rows selected. |
这是因为存在聚簇(cluster),有关内容见第10章的讨论,其中可能包含多个表。因此,从X$BH联结DBA_OBJECTS来打印一个段名时,从技术上讲,我们必须列出聚簇中所有对象的所有名字,因为数据库块并不一直属于一个表。
甚至对于重复查询的块,我们也可以观察Oracle如何递增这个块的接触计数。在这个例子中我们使用了一个神奇的表DUAL,可以知道这是一个只有一行一列的表。我们想得出这一行的块信息。内置的DBMS_ROWID包就很适合得到这个信息。另外,由于我们要从DUAL查询ROWID,所以Oracle会从缓冲区缓存读取真正的DUAL表,而不是Oracle
注意 在Oracle
所以,每次运行以下查询时,都会命中真正的DUAL表:
sys@ORA9IR2> select tch, file#, dbablk, DUMMY 2 from x$bh, (select dummy from dual) 3 where obj = (select data_object_id 4 from dba_objects 5 where object_name = 'DUAL' 6 and data_object_id is not null) 7 /
TCH FILE# DBABLK D ---------- ---------- ---------- - 1 1 1617 X 0 1 1618 X sys@ORA9IR2> exec dbms_lock.sleep(3.2); PL/SQL procedure successfully completed. sys@ORA9IR2> / TCH FILE# DBABLK D ---------- ---------- ---------- - 2 1 1617 X 0 1 1618 X sys@ORA9IR2> exec dbms_lock.sleep(3.2); PL/SQL procedure successfully completed. sys@ORA9IR2> / TCH FILE# DBABLK D ---------- ---------- ---------- - 3 1 1617 X 0 1 1618 X sys@ORA9IR2> exec dbms_lock.sleep(3.2); PL/SQL procedure successfully completed. sys@ORA9IR2> / TCH FILE# DBABLK D ---------- ---------- ---------- - 4 1 1617 X 0 1 1618 X |
在不同的Oracle版本上,输出可能不同,你可能还会看到返回不止两行。也许你观察到TCH并没有每次都递增。在一个多用户系统上,结果可能更难预料。Oracle试图每3秒将TCH递增一次(还有一个TIM列,它会显示对TCH列最后一次更新的时间),但是这个数是否100%正确并不重要,只要接近就行。另外,Oracle会有意地“冷却”块,过一段时间会让TCH计数递减。所以,如果你在自己的系统上运行这个查询,可能会看到完全不同的结果。
因此,在Oracle8i 及以上版本中,块缓冲区不再像以前那样移到块列表的最前面;而是原地留在块列表中,只是递增它的接触计数。不过,过一段时间后,块会很自然地在列表中“移动”。这里把“移动”一词用引号括起来,这是因为块并不是物理地移动;只是因为维护了多个指向块的列表,所以块会在这些列表间“移动”。例如,已修改的块由脏列表指示(要由DBWn写至磁盘)。过一段时间要重用块时,如果缓冲区缓存满了,就要将接触计数较小的某个块释放,将其“放回到”新数据块列表的接近于中间的位置。
管理这些列表的整个算法相当复杂,而且随着Oracle版本的变化也在变化,并不断改进。作为开发人员,我们并不需要关心所有细节,只要知道频繁使用的块会被缓存,不常使用的块不会缓存太久,这就够了。
2. 多个块大小
从Oracle9i开始,同一个数据库中可以有多个不同的数据库块大小。此前,一个数据库中的所有块大小都相同,要想使用一个不同的块大小,必须重新建立整个数据库。现在就不同了,你可以有一个“默认的”块大小(最初创建数据库时使用的块大小;即SYSTEM和所有TEMPORARY表空间的块大小),以及最多4个其他的块大小。每个不同的块大小都必须有其自己的缓冲区缓存。默认池、保持池和回收池只缓存具有默认大小的块。为了在数据库中使用非默认的块大小,需要配置一个缓冲区池来保存这些块。
在这个例子中,我的默认块大小是8 KB。我想创建一个块大小为16 KB的表空间:
ops$tkyte@ORA
2 datafile size
3 blocksize 16k; create tablespace ts_16k * ERROR at line 1: ORA-29339: tablespace blocksize 16384 does not match configured blocksizes ops$tkyte@ORA
NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ db_16k_cache_size big integer 0 |
现在,由于我还没有配置一个16 KB的缓存,所以无法创建这样一个表空间。要解决这个问题,可以在以下方法中选择一种。我可以设置DB_16K_CACHE_SIZE参数,并重启数据库。也可以缩小另外的某个SGA组件,从而在现有的SGA中腾出空间来建立一个16 KB的缓存。或者,如果SGA_MAX_SIZE参数大于当前的SGA大小,我还可以直接分配一个16 KB的缓存。
注意 从Oracle9i开始,即使数据库已经启动并且正在运行,你也能重新设置各个SGA组件的大小。如果你想拥有这个能力,能够“扩大”SGA的大小(超过初始分配的大小),就必须把SGA_MAX_SIZE参数设置为大于已分配SGA的某个值。例如,如果启动之后,你的SGA大小为128 MB,你想再为缓冲区缓存增加另外的64 MB,就必须把SGA_MAX_SIZE设置为192 MB或更大,以便扩展。
在这个例子中,我采用收缩的办法,即缩小我的DB_CACHE_SIZE,因为目前这个参数设置得太大了:
ops$tkyte@ORA
NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ db_cache_size big integer
ops$tkyte@ORA
System altered. ops$tkyte@ORA
System altered. ops$tkyte@ORA
2 datafile size
3 blocksize 16k; Tablespace created. |
这样一来,我就建立了另外一个缓冲区缓存,要用来缓存16 KB大小的块。默认池(由db_cache_size参数控制)大小为768 MB,16 KB缓存(由db_16k_cache_size参数控制)大小为256 MB。这两个缓存是互斥的,如果一个“填满”了,也无法使用另一个缓存中的空间。这样DBA就能很精细地控制内存的使用,但是这也是有代价的。代价之一就是复杂性和管理。使用多个块大小的目的并不是为了性能或作为一个调优特性,而是为了支持可传输的表空间,也就是可以把格式化的数据文件从一个数据库传输或附加到另一个数据库。比如说,通过实现多个块大小,可以取得一个使用8 KB块大小的事务系统中的数据文件,并将此信息传输到使用16 KB或32 KB块大小的数据仓库。
不过,对于测试来说,有多个块大小很有好处。如果你想看看你的数据库如何处理另一个块大小,例如,如果使用4 KB的块而不是8 KB的块,一个表会占用多大的空间。现在由于可以支持多个块大小,你就能很轻松地进行测试,而不必创建一个全新的数据库实例。
还可以把多个块大小用作一种精细调优工具,对一组特定的段进行调优,也就是为这些段分配各自的私有缓冲区池。或者,在一个具有事务用户的混合系统中,事务用户可能使用一组数据,而报告/仓库用户查询另外一组单独的数据。如果块比较小,这对事务数据很有好处,因为这样会减少块上的竞争(每个块上的数据/行越少,就意味着同时访问同一个块的人越少),另外还可以更好地利用缓冲区缓存(用户只向缓存读入他们感兴趣的数据,可能只有一行或者很少的几行)。报告/仓库数据(可能以事务数据为基础)则不同,块更大一些会更好,其部分原因在于这样块开销会较少(所占的总存储空间较小),而且逻辑I/O处理的数据更多。由于报告/仓库数据不存在事务数据那样的更新竞争问题,所以如果每个块上有更多的行,这并不是问题,反而是一个优点。另外,事务用户实际上会得到自己的缓冲区缓存;他们并不担心报告查询会过分占用缓存。
但是一般来讲,默认池、保持池和回收池对于块缓冲区缓存精细调优来说应该已经足够了,多个块大小主要用于从一个数据库向另一个数据库传输数据,可能在混合的报告/事务系统中也会用到这种机制。
4.2.4 共享池
共享池是SGA中最重要的内存段之一,特别是对于性能和可扩缩性来说。共享池如果太小,会严重影响性能,甚至导致系统看上去好像中止了一样。如果共享池太大,也会有同样的效果。共享池使用不当会导致灾难性的后果。
那么,到底什么是共享池?共享池就是Oracle缓存一些“程序”数据的地方。在解析一个查询时,解析得到的表示(representation)就缓存在那里。在完成解析整个查询的任务之前, Oracle会搜索共享池,看看这个工作是否已经完成。你运行的PL/SQL代码就在共享池中缓存,所以下一次运行时,Oracle不会再次从磁盘重新读取。PL/SQL代码不仅在这里缓存,还会在这里共享。如果有1 000个会话都在执行同样的代码,那么只会加载这个代码的一个副本,并由所有会话共享。Oracle把系统参数存储在共享池中。数据字典缓存(关于数据库对象的已缓存信息)也存储在这里。简单地讲,就像是厨房的水池一样,什么东西都往共享池里放。
共享池的特点是有大量小的内存块(chunk),一般为4 KB或更小。要记住,4 KB并不是一个硬性限制,可能有的内存分配会超过这个大小,但是一般来讲,我们的目标是使用小块的内存来避免碎片问题。如果分配的内存块大小显著不同(有的很小,有的却相当大),就可能出现碎片问题。共享池中的内存根据LRU(最近最少使用)的原则来管理。在这方面,它类似于缓冲区缓存,如果你不用某个对象,它就会丢掉。为此提供了一个包,名叫DBMS_SHARED_POOL,这个包可用于改变这种行为,强制性地“钉住”共享池中的对象。可以使用这个过程在数据库启动时加载频繁使用的过程和包,并使它们不至于老化。不过,通常如果过一段时间共享池中的一段内存没有得到重用,它就会老化。甚至PL/SQL代码(可能相当大)也以一种分页机制来管理,这样当你执行一个非常大的包中的代码时,只有所需的代码会加载到共享池的小块中。如果你很长时间都没有用它,而且共享池已经填满,需要为其他对象留出空间,它就会老化。
如果你真的想破坏Oracle的共享池,最容易的办法是不使用绑定变量。在第1章已经看到,如果不使用绑定变量,可能会让系统陷于瘫痪,这有两个原因:
q 系统要花大量CPU时间解析查询。
q 系统使用大量资源来管理共享池中的对象,因为从来不重用查询。
如果提交到Oracle的每个查询都是具有硬编码值的惟一查询,则共享池的概念就一点用都没有。设计共享池是为了反复使用查询计划。如果每个查询都是全新的,都是以前从来没有见过的查询,那么缓存只会增加开销。共享池反而会损害性能。为了解决这个问题,很多人都会用一种看似合理的常用技术,也就是向共享池增加更多的空间,但是这种做法一般只会使问题变得比以前更糟糕。由于共享池不可避免地会再次填满,比起原来较小的共享池来说,开销甚至更大,原因很简单,与管理一个较小的满共享池相比,管理一个大的满共享池需要做更多的工作。
对于这个问题,真正的解决方案只有一个,这就是使用共享SQL,也就是重用查询。在前面(第1章),我们简要介绍了参数CURSOR_SHARING,在这方面,游标共享可以作为一种短期的解决方案。不过,真正要解决这个问题,首当其冲地还是要使用可重用的SQL。即使在最大的系统上,我发现一般也最多有10 000~20 000条不同的SQL语句。大多数系统只执行数百个不同的查询。
下面是一个真实的示例,从这个例子可以看出,如果共享池使用不当后果会有多严重。我曾参与过这样一个系统,它的标准操作过程是每天晚上关闭数据库,清空SGA,再重启。之所以这样做只是因为,系统白天运行时有问题,会完全占用CPU,所以如果数据库运行的时间超过一天,性能就开始严重下降。他们原来在一个1.1 GB的SGA中使用了1 GB的共享池。确实如此:0.1 GB由块缓冲区缓存和其他元素专用,另外1 GB则完全用于缓存不同的查询,但这些查询从来都不会再次执行。必须冷启动的原因是,如果让系统运行一天以上的时间,就会用光共享池中的空闲内存。此时,结构老化的开销就太大了(特别是对于一个如此大的结构),系统会为此疲于奔命,而性能也会大幅恶化(不过原来的性能也好不到哪里去,因为他们管理的是一个1 GB的共享池)。另外,使用这个系统的人一直想向机器增加越来越多的CPU,因为硬解析SQL太耗费CPU。通过对应用进行修正,让它使用绑定变量,不仅消除了物理的硬件需求(现在的CPU能力比他们实际需要的已经高出几倍),而且对各个池的内存分配也反过来了。现在不是使用1 GB的共享池,而只为共享池分配了不到100 MB的空间,即使是经过数周连续地运行,也不会用光共享池的内存。
关于共享池和参数SHARED_POOL_SIZE还有一点要说明。在Oracle9i及以前的版本中,查询的结果与SHARED_POOL_SIZE参数之间没有直接的关系,查询结果是:
ops$tkyte@ORA9IR2> select sum(bytes) from v$sgastat where pool = 'shared pool'; SUM(BYTES) ------------------ 100663296 |
SHARED_POOL_SIZE参数是:
ops$tkyte@ORA9IR2> show parameter shared_pool_size NAME TYPE VALUE ------------------------------------ -------------- ------------------------------ shared_pool_size big integer 83886080 |
如果实在要谈谈它们的关系,只能说SUM(BYTES) FROM V$SGASTAT总是大于SHARED_ POOL_SIZE。共享池还保存了另外的许多结构,它们不在相应参数的作用域内。SHARED_POOL_SIZE通常占了共享池(SUM(BYTES)报告的结果)中最大的一部分,但这不是共享池中惟一的一部分。例如,参数CONTROL_FILES就为共享池“混合”部分做出了贡献,每个文件有264字节。遗憾的是,V$SGASTAT中的“共享池”与参数SHARED_POOL_SIZE的命名让人很容易混淆,这个参数对共享池大小贡献最大,但是它并不是惟一有贡献的参数。
不过,在Oracle
ops$tkyte@ORA
2 from v$sgastat where pool = 'shared pool'; MBYTES ---------- 128 ops$tkyte@ORA
NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ shared_pool_size big integer
|
如果你从Oracle9i或之前的版本转向
4.2.5 大池
大池(large pool)并不是因为它是一个“大”结构才这样取名(不过,它可能确实很大)。之所以称之为大池,是因为它用于大块内存的分配,共享池不会处理这么大的内存块。
在Oracle 8.0引入大池之前,所有内存分配都在共享池中进行。如果你使用的特性要利用“大块的”内存分配(如共享服务器UGA内存分配),倘若都在共享池中分配就不太好。另外,与共享池管理内存的方式相比,处理(需要大量内存分配)会以不同的方式使用内存,所以这个问题变得更加复杂。共享池根据LRU来管理内存,这对于缓存和重用数据很合适。不过,大块内存分配则是得到一块内存后加以使用,然后就到此为止,没有必要缓存这个内存。
Oracle需要的应该是像为块缓冲区缓存实现的回收和保持缓冲区池之类的组件。这正是现在的大池和共享池。大池就是一个回收型的内存空间,共享池则更像是保持缓冲区池。如果对象可能会被频繁地使用,就将其缓存起来。
大池中分配的内存在堆上管理,这与C语言通过malloc()和free()管理内存很相似。一旦“释放”了一块内存,它就能由其他进程使用。在共享池中,实际上没有释放内存块的概念。你只是分配内存,然后使用,再停止使用而已。过一段时间,如果需要重用那个内存,Oracle会让你的内存块老化。如果只使用共享池,问题在于:一种大小不一定全局适用。
大池专门用于以下情况:
q 共享服务器连接,用于在SGA中分配UGA区
q 语句的并行执行,允许分配进程间的消息缓冲区,这些缓冲区用于协调并行查询服务器。
q 备份,在某些情况下用于RMAN磁盘I/O 缓冲区。
可以看到,这些内存分配都不应该在LRU缓冲区池中管理,因为LRU缓冲区池的目标是管理小块的内存。例如,对于共享服务器连接内存,一旦会话注销,这个内存就不会再重用,所以应该立即返回到池中。另外,共享服务器UGA内存分配往往“很大”。如果查看前面使用SORT_AREA_RETAINED_SIZE或PGA_AGGREGATE_TARGET的例子,可以看到,UGA可能扩张得很大,成为绝对大于4 KB的块。把MTS内存放在共享池中,这会导致把它分片成很小的内存,不仅如此,你还会发现从不重用的大段内存会导致可能重用的内存老化。这就要求数据库以后多做更多的工作来重建内存结构。
对于并行查询消息缓冲区也是如此,因为它们不能根据LRU原则来管理。并行查询消息缓冲区可以分配,但是在使用完之前不能释放。一旦发送了缓冲区中的消息,就不再需要这个缓冲区,应该立即释放。对于备份缓冲区更是如此,备份缓冲区很大,而且一旦Oracle用完了这些缓冲区,它们就应该“消失”。
使用共享服务器连接时,并不是一定得使用大池,但是强烈建议你使用大池。如果没有大池,而且使用了一个共享服务器连接,就会像Oracle 7.3及以前版本中一样从共享池分配空间。过一段时间后,这会导致性能恶化,一定要避免这种情况。如果DBWR_IO_SLAVES或者PARALLEL_MAX_SERVERS参数设置为某个正值,大池会默认为某个大小。如果你使用了一个用到大池的特性,建议你手动设置大池的大小。默认机制一般并不适合你的具体情况。
4.2.6 Java池
Java池(Java pool)是Oracle
Java池有多种用法,这取决于Oracle服务器运行的模式。如果采用专用服务器模式,Java池包括每个Java类的共享部分,由每个会话使用。这些实质上是只读部分(执行向量、方法等),每个类的共享部分大约4~8 KB。
因此,采用专用服务器模式时(应用使用纯Java存储过程时往往就会出现这种情况),Java池所需的总内存相当少,可以根据要用的Java类的个数来确定。应该知道,如果采用专用服务器模式,每个会话的状态不会存储在SGA中,因为这个信息要存储在UGA中,你应该记得,使用专用服务器模式时,UGA包括在PGA中。
使用共享服务器连接来连接Oracle时,Java池包括以下部分:
q 每个Java类的共享部分。
q UGA中用于各会话状态的部分,这是从SGA中的JAVA_POOL分配的。UGA中余下的部分会正常地在共享池中分配,或者如果配置了大池,就会在大池中分配。
由于Oracle9i及以前版本中Java池的总大小是固定的,应用开发人员需要估计应用的总需求,再把估计的需求量乘以所需支持的并发会话数,所得到的结果能指示出Java池的总大小。每个Java UGA会根据需要扩大或收缩,但是要记住,池的大小必须合适,所有UGA加在一起必须能同时放在里面。在 Oracle
4.2.7 流池
流池(stream pool)是一个新的SGA结构,从Oracle
注意 上面提到了流“是Oracle在数据复制方面发展的方向”,这句话不能解释为高级复制(Advanced Replication,这是Oracle现有的复制特性)会很快过时。相反,将来几个版本中还会支持高级复制。要了解流本身的更多内容,请参考Streams Concepts Guide (在http://otn.oracle.com的Documentation部分)。
流池(或者如果没有配置流池,则是共享池中至多10%的空间)会用于缓存流进程在数据库间移动/复制数据时使用的队列消息。这里并不是使用持久的基于磁盘的队列(这些队列有一些附加的开销),流使用的是内存中的队列。如果这些队列满了,最终还是会写出到磁盘。如果使用内存队列的Oracle实例由于某种原因失败了,比如说因为实例错误(软件瘫痪)、掉电或其他原因,就会从重做日志重建这些内存中的队列。
因此,流池只对使用了流数据库特性的系统是重要的。在这些环境中,必须设置流池,以避免因为这个特性从共享池“窃取”10%的空间。
4.2.8 自动SGA内存管理
与管理PGA内存有两种方法一样,从Oracle
注意 在Oracle9i及以前版本中,只能用手动SGA内存管理,不存在参数SGA_TARGET,而且参数 SGA_MAX_SIZE只是一个上限,而不是动态目标。
在Oracle
q 自动调优的SGA参数:目前这些参数包括DB_CACHE_SIZE、SHARED_POOL_SIZE、LARGE_POOL_SIZE和JAVA_POOL_SIZE。
q 手动SGA参数:这些参数包括LOG_BUFFER、STREAMS_POOL、DB_NK_CACHE_SIZE、DB_KEEP_CACHE_SIZE和DB_RECYCLE_CACHE_SIZE。
在Oracle
注意 要使用自动SGA内存管理,参数STATISTICS_LEVEL必须设置为TYPICAL或ALL。如果不支持统计集合,数据库就没有必要的历史信息来确定大小。
采用自动SGA内存管理时,确定自动调整组件大小的主要参数是SGA_TARGET,这个参数可以在数据库启动并运行时动态调整,最大可以达到SGA_MAX_SIZE参数设置的值(默认等于SGA_TARGET,所以如果想增加SGA_TARGET,就必须在启动数据库实例之前先把SGA_MAX_SIZE设置得大一些)。数据库会使用SGA_TARGET值,再减去其他手动设置组件的大小(如DB_KEEP_CACHE_SIZE、DB_RECYCLE_CACHE_SIZE等),并使用计算得到的内存量来设置默认缓冲区池、共享池、大池和Java池的大小。在运行时,实例会根据需要动态地对这4个内存区分配和撤销内存。如果共享池内存用光了,实例不会向用户返回一个ORA-04031“Unable to allocate N bytes of shared memory”(无法分配N字节的共享内存)错误,而是会把缓冲区缓存缩小几MB(一个颗粒的大小),再相应地增加共享池的大小。
随着时间的推移,当实例的内存需求越来越确定时,各个SGA组件的大小也越来越固定。即便数据库关闭后又启动,数据库还能记得组件的大小,因此不必每次都从头再来确定实例的正确大小。这是通过4个带双下划线的参数做到的:__DB_CACHE_SIZE、__JAVA_POOL_SIZE、__LARGE_POOL_SIZE和__SHARED_POOL_SIZE。如果正常或立即关闭数据库,则数据库会把这些值记录到存储参数文件(SPFILE)中,并在启动时再使用这些值来设置各个区的默认大小。
另外,如果知道4个区中某个区的最小值,那么除了设置SGA_TARGET外,还可以设置这个参数。实例会使用你的设置作为下界(即这个区可能的最小大小)。
4.3 小结
这一章介绍了Oracle内存结构。首先从进程和会话级开始,我们分析了PGA和UGA以及它们的关系。还了解到连接Oracle的模式可以指示内存组织的方式。相对于共享服务器连接来说,专用服务器连接表示服务器进程中会使用更多的内存,但是使用共享服务器连接的话,则说明需要的SGA大得多。接下来,我们讨论了SGA本身的主要结构,揭示了共享池和大池之间的区别,并说明为什么希望有一个大池来“节省”我们的共享池。我们还介绍了Java池,以及在各种情况下如何使用Java池。此外还分析了块缓冲区缓存,以及如何将块缓冲区缓存划分为更小、更“专业”的池。
下面可以转向Oracle实例的余下一部分,即构成Oracle实例的物理进程。