
1.2 实例的内存结构
当实例启动时,系统为实例分配了一段内存空间,并启动若干后台进程。内存空间分成不同的部分,分别用来存储不同的信息。具体来说,在这段内存空间中存储以下信息:
●程序代码:Oracle的可执行代码;
●缓冲数据:用户要访问的数据、重做日志等。这部分内存叫作SGA;
●与会话有关的信息;
●与进程间通信有关的信息,如加锁的信息。
在上述内存区域中,最重要的是SGA。SGA是由多个缓存和缓冲池组成的,在这些内存结构中存储不同类型的数据。根据存储数据的类型,SGA中主要包含以下类型的内存结构:
●数据库高速缓存;
●重做日志缓冲区;
●共享池;
●Java池;
●大池。
其中,数据库高速缓存由许多缓冲区组成,共享池由数据字典缓存和库缓存两部分组成。在这里之所以使用了缓存和缓冲区两个概念,是因为它们来自不同的英文单词,缓存是从单词cache翻译过来的,而缓冲区来自单词buffer。
当实例运行时,可以通过命令查看SGA的大小。SGA由不同的内存结构组成,所以查看的结果是分别列出了不同组成部分的大小。例如,在SQL*Plus中执行SHOW命令可以查看当前SGA的大小,如图1-3所示。

图1-3
SGA更详细的信息可以从动态性能视图v$sga、v$sgainfo、v$sgastat中获得。从这些视图中可以获得每种缓冲区和缓存的大小信息。
下面开始介绍实例内存结构的“数据库高速缓存”“重做日志缓冲区”“共享池”“Java池”及“PGA(程序全局区)”。
1.2.1 数据库高速缓存
数据库高速缓存是SGA中的一段存储区域,用来存放用户最近访问的数据。当用户访问数据文件中的数据时,服务器进程首先查看这样的数据是否已经存在于数据库高速缓存中。如果是,则直接在数据库高速缓存对数据进行访问,并将处理结果返回给用户,这次数据访问叫作“命中”,这样的读操作称为“逻辑读(Logical Reads)”。否则,服务器进程将数据从数据文件的数据块中读到数据库高速缓存中,然后在数据库高速缓存对数据进行访问,这次数据访问叫作“未命中”,这样的读操作称为“物理读(Physical Reads)”。显然,如果直接在数据库高速缓存访问数据,要比从数据文件中读数据快得多。所以,访问数据的命中率越高,数据库的性能就越高。对数据库进行性能优化的一个重要方面就是提高逻辑读在所有读操作中所占的比例。
数据库高速缓存的大小通过初始化参数DB_CACHE_SIZE来指定。提高数据访问命中率最直接的方法是增加数据库高速缓存的大小,但它的大小不能无限制地增加,它要受到物理内存大小的限制。
用户访问的数据都存储在数据文件中,数据文件被划分为许多大小相同的数据块。数据块是Oracle进行读/写的基本单位,也就是说,即使用户只希望访问一个字节的数据,那么数据所在的整个数据块也将被读到数据高速缓存中。同样,在写数据时,也是以数据块为单位的。这样做的好处是提高了数据库服务器的吞吐量。
数据库高速缓存是由一个个的缓冲区组成的,数据从数据文件中被读到数据库高速缓存中之后,就放在这些缓冲区中。缓冲区的大小与数据块的大小一致,一个数据块的内容恰好放在一个缓冲区中。数据块的大小由初始化参数db_block_size指定,并且在数据库创建后不能被修改,那么缓冲区的大小也由这个参数决定。所有缓冲区大小的总和由初始化参数DB_CACHE_SIZE指定。假设在参数文件中定义了以下两个初始化参数:
●DB_BLOCK_SIZE=8192
●DB_CACHE_SIZE=25165824
这说明每个缓冲区的大小为8192字节,即8KB,而所有缓冲区总的大小为25165824字节,即24MB,这也是整个数据库高速缓存的大小。由此可以计算组成整个数据库高速缓存的缓冲区个数,即25165824/8192=3072个。
前面已经提到,提高数据访问命中率的一个有效方法是增加数据库高速缓存的大小。但是由于受到物理内存的限制,数据库高速缓存不可能无限大。如果将数据库高速缓存设置得过大,操作系统可以使用的内存将减少,这样就要使用虚拟内存,反而会降低系统性能。所以数据访问的命中率不可能达到100%。如果用户访问的数据不在数据库高速缓存中,就需要把数据读到某个缓冲区中。一个数据块的内容到底放在哪个缓冲区中,如果没有足够的空闲缓冲区该怎么办呢?有必要分析一下缓冲区的使用情况。
根据缓冲区的使用情况,可以把缓冲区分为空闲缓冲区、脏缓冲区和忙缓冲区3种类型。如果一个缓冲区中存放的是由SELECT命令检索的数据,而且这样的数据没有被修改过,这样的缓冲区就是空闲缓冲区。如果缓冲区中存放的是由DML命令处理过的数据,而且这样的数据已经被写入数据块中,这样的缓冲区也是空闲缓冲区。空闲缓冲区的内容与对应数据块中的内容完全一致,这样的缓冲区可用来存放用户即将访问的数据。如果用户执行了INSERT、UPDATE或者DELETE命令,相应的访问将在缓冲区中进行。如果数据被修改后还没有写入数据块,这时缓冲区中的内容与数据块不一致,这样的缓冲区就是脏缓冲区。脏缓冲区中的数据必须写入数据文件的数据块中,这个任务由后台进程DBWR(数据库写进程)完成。脏缓冲区中的数据被写入数据块后,脏缓冲区又成为空闲缓冲区。
忙缓冲区是指正在被访问的缓冲区,两个用户进程不能同时访问同一个忙缓冲区。如果用户进程要访问的数据不在缓冲区中,服务器进程将把对应数据块中的数据读入数据库高速缓存的空闲缓冲区中,这个缓冲区中以前的数据将被覆盖。现在考虑一种情况:假设一个缓冲区中的内容被覆盖,恰在这时,另一个用户进程要再一次访问这个缓冲区中以前的数据,服务器进程将不得不把数据所在的数据块重新从数据文件读到另一个缓冲区中,这种情况显然会降低数据库的性能。为了确保数据访问的命中率,Oracle采用LRU(Least Recently Used,最近最少使用)算法,确定每次使用的空闲缓冲区。LRU算法的思想是基于这样一个假设:在最近一段时间内使用最少的缓冲区,在以后的一段时间内也将使用最少,而最近一段时间被频繁访问的缓冲区,在以后的一段时间也将被频繁访问,这个缓冲区中的数据就不应该被覆盖。基于这个思想,每次将数据读到缓冲区中时,服务器进程总是选择那些最近访问次数最少的空闲缓冲区。这种方法虽然不能完全杜绝,但是可以尽量减少上述情况的发生。
在实例中维护了一个LRU队列,在这个队列中记录了各个缓冲区的使用情况。队列的操作遵循“先进先出”的原则,那些最近访问最频繁的缓冲区位于队列尾部,而那些最近最少被访问的缓冲区则位于队列头部。
如果用户访问的数据恰好在缓冲区中,则该缓冲区被标志为“最近访问”,并被移动到队列尾部。如果用户访问的数据不在缓冲区中,服务器进程将在队列头部寻找合适数量的空闲缓冲区,将数据读到这些缓冲区中,并将它们标志为“最近访问”,然后将它们移动到队列尾部。
如果在搜索LRU队列的过程中遇到一个忙缓冲区,服务器进程将忽略它。如果找到一个脏缓冲区,服务器进程将这个脏缓冲区写入另外一个“脏队列”中,然后继续查找LRU队列,直到找到足够数量的空闲缓冲区。在脏队列中记录了数据库高速缓存中的脏缓冲区,实例中的DBWR(数据库写进程)后台进程在一定的时机下将这个队列中的缓冲区写入数据文件中。
上面考虑了缓冲区的一种使用情况,即在数据库高速缓存总有足够数量的空闲缓冲区。一种经常发生的情况是,在搜索LRU队列时没有找到足够数量的空闲缓冲区,这时服务器进程将激活实例中的DBWR(数据库写进程)后台进程,将脏队列中的脏缓冲区内容写入数据文件中,这些脏缓冲区重新成为空闲缓冲区,它们将被从脏队列中清除,而重新被写入LRU队列,服务器进程将继续在LRU队列中搜索。
当用户进程执行事务时,将在数据库高速缓存中产生脏缓冲区,这些脏缓冲区并不是立刻被写入数据文件,而是在一定的时机下,由DBWR(数据库写进程)进程一起写入,这样做的好处是减少了磁盘I/O,从而提高了数据库的性能。
前面说过,数据块的大小由初始化参数DB_BLOCK_SIZE指定,而且不能改变。设置数据块大小的一个基本原则是:如果在数据库中主要执行SELECT语句,如在数据仓库中,这个参数可以设置得大一些;如果主要执行DML(insert、update、delete)语句,这个参数可以设置得小一些。但是在一个数据库中,可能会对一部分数据主要执行SELECT语句,对另一部分数据主要执行DML(insert、update、delete)语句。在这种情况下,可以在数据库中定义不同的数据块大小,根据数据的不同访问要求,把它们放在不同的数据块中,从而在整体上提高数据库的性能。
由初始化参数DB_BLOCK_SIZE指定的数据块称为标准块,在数据库中还可以定义其他大小的非标准数据块,非标准数据块的大小可以是2KB、4KB、8KB、16KB、32KB等。为了访问非标准块中的数据,在SGA中也需要为它们定义相应的数据库高速缓存,而缓存中缓冲区大小与非标准块的大小也是一致的。Oracle提供了一套新的初始化参数DB_nK_CACHE_SIZE,用于定义与nKB的非标准数据块对应的数据库高速缓存大小,例如:
●DB_2K_CACHE_SIZE为2KB的数据块定义缓存大小,缓存由2KB的缓冲区组成;
●DB_4K_CACHE_SIZE为4KB的数据块定义缓存大小,缓存由4KB的缓冲区组成;
●DB_8K_CACHE_SIZE为8KB的数据块定义缓存大小,缓存由8KB的缓冲区组成;
……
例如,假设在参数文件中有以下初始化参数。
●DB_BLOCK_SIZ=8192
●DB_CACHE_SIZE=25165824
●DB_2K_CACHE_SIZE=48M
●DB_16K_CACHE_SIZE=56M
这说明标准数据块的大小为8KB,由8KB的缓冲区组成的数据库高速缓存为24MB,同时定义了两种大小的非标准块和对应的缓存,由2KB的缓冲区组成的数据库高速缓存大小为48MB,由16KB的缓冲区组成的数据库高速缓存大小为56MB,总的数据库高速缓存大小为三者之和。
注意:在上面这种情况下使用初始化参数DB_8K_CACHE_SIZE是非法的,因为标准块的大小为8KB。
为了使用非标准数据块,首先需要定义对应的数据库高速缓存,然后在创建表空间时为数据文件指定数据块大小。当用户访问标准数据块中的数据时,数据将被读入与标准块大小一致的缓冲区中。同样,非标准数据块中的数据将被读入与非标准块大小一致的缓冲区中。
在SQL*Plus中可以通过SHOW命令查看数据块的大小和每种缓冲区的大小,默认缓冲区的大小与标准数据块的大小一致。例如,下面的命令将显示标准数据块的大小。
SQL>SHOW PARAMETER DB_BLOCK_SIZE NAME TYPE VALUE [代码编号0001]
运行结果如图1-4所示。

图1-4
下面的命令将显示16KB缓冲区的大小:
SQL>SHOW PARAMETER DB 16K CACHE SIZE NAME TYPE VALUE [0002]
1.2.2 重做日志缓冲区
重做日志是对用户事务所产生的记录,通过重做日志能够重新产生数据,它是确保数据安全的一种重要方法。当用户执行DML(Data Manipulation Language,数据操纵语言,主要包括INSERT、UPDATE和DELETE),或者DDL(Data Defination Language;数据定义语言,主要包括CREATE、DROP、ALTER等)操作时,服务器进程首先将这些操作记录在重做日志缓冲区中,然后才去修改相应的数据。重做日志缓冲区中的内容在一定的时机下,被LGWR(日志写进程)后台进程写入重做日志文件。如果数据库系统出现故障,可以根据重做日志文件中的重做日志对数据库进行恢复。
引入重做日志缓冲区的好处是显而易见的:将日志记录在重做日志缓冲区中,比直接写入重做日志文件要快得多。另外,LGWR(日志写进程)并不是在每次用户访问数据之后,都要将重做日志缓冲区中的日志写入重做日志文件,而是将最近一段时间产生的重做日志一起写入,这样可以减少访问磁盘的次数,从而提高系统的性能。
重做日志缓冲区的大小由初始化参数LOG_BUFFER指定。在一定的时机下,LGWR(日志写进程)后台进程会将重做日志缓冲区中的内容写入重做日志文件。例如,在重做日志缓冲区被消耗了三分之一时。由此可见,重做日志缓冲区越大,就可以记录越多的用户操作,写重做日志文件的次数也就越少,这样也可以提高数据库的性能。当然,重做日志缓冲区的大小是受物理内存大小的限制的。
1.2.3 共享池
数据库中的数据是以表的形式组织在一起的。当用户访问数据时,数据库服务器首先检查对应的表是否存在,然后检查指定的列是否存在,还要检查权限和加锁等信息。当这些检查都通过时,数据库服务器将对用户的命令进行分析,产生分析代码和执行计划,然后按照这样的执行计划访问数据,并将执行结果返回给用户。
为了提高数据库的性能,Oracle在SGA中开辟了一个共享池,用于存放与SQL语句的执行有关的信息。共享池主要由3部分组成,即数据字典高速缓存、库高速缓存和服务器结果缓存。共享池的大小由初始化参数SHARED_POOL_SIZE指定。总的来说,共享池几乎和数据库中的所有操作都有关。
当用户访问数据库中的数据时,数据库服务器首先要查询相关的数据字典,确定要访问的对象是否存在,如表、视图等,以及表和视图上的列等。然后检查权限等信息,最后才执行这样的命令。数据字典信息存储在SYSTEM表空间中,也就是说,存储在磁盘上的数据文件中。如果数据库服务器进程每次都要从磁盘上读数据字典信息,那么SQL语句的执行效率很低。
为了加快这个SQL语句的执行速度,Oracle在共享池中开辟了数据字典缓存,用来存放最近访问的数据字典的信息。这样,在查询相关的数据字典时,可以直接在数据字典缓存中进行。
库高速缓存用于存放最近执行的SQL命令的相关信息。数据库服务器在执行SQL命令时,首先要对SQL命令进行解析,产生分析代码,并生成执行计划,然后才按照执行计划执行SQL命令,最后把命令的执行结果返回给用户。在整个执行过程中,分析阶段所用的时间最长。如果能减少分析所用的时间,那么整个SQL命令的执行效率将大大提高。Oracle的做法是:在执行一条SQL命令时,服务器进程对它进行分析,然后把SQL命令文本、解析结果和执行计划存储在库高速缓存中。
服务器进程在执行一条SQL命令时,首先要到库高速缓存中查看是否存在这条SQL命令的信息,如果发现SQL语句相关信息已经存储在库高速缓存中,就直接取出执行计划并执行它,这样就省去了分析所用的时间,可以大大加快SQL命令的执行速度。否则,服务器进程需要按部就班地对SQL命令进行解析,然后生成执行计划,并将这些信息存储在库高速缓存中。
当两条SQL命令的文本完全相同时,认为它们是同一条命令,这时它们可以共享库高速缓存中的信息。如果命令文本中的大小写、空格个数或参数的数值不同,就认为是两条不同的命令,它们在执行时需要单独进行分析,最终将生成不同的分析代码和执行计划。例如,考虑以下两条命令:
SELECT ename,sal FROM emp WHERE eno=7902
SELECT ENAME,SAL FROM EMP WHERE EN0=7902 [0003]
这两条命令看似相同,实际上是有差别的。首先,命令中的大小写不同。其次,两个列之间的空格个数不同,所以它们将生成不同的分析代码和执行计划。由此可见,命令的书写风格对命令的执行结果没有什么影响,但对命令的执行效率却大有影响。
在这里,给程序员一个建议:开发程序时要考虑编程的风格和程序的执行效率。虽然每个程序员都会编程,但是不同的程序员编写的程序的执行效率是大不一样的。实践证明,在很多生产系统中,程序员编写的程序就是整个系统性能的瓶颈,这样的系统在提交用户后,管理员对此是无能为力的。
1.2.4 Java池
Java池是SGA中两段可选的存储区域。如果要在数据库中运行Java应用程序,那么对用户的每个会话来说,都需要一个单独的Java虚拟机。实际情况是,每个Java虚拟机仅仅需要很小的一段内存空间,大约35KB。Java虚拟机为什么能在这么小的内存空间中运行呢?
在实例启动时,可以在SGA中分配一个Java池,用来存放运行Java所必需的共享代码和共享数据。多个Java应用程序可以共享Java池中的代码和数据。在有些情况下,每个用户的Java会话信息也存储在Java池中。
Java池的大小由初始化参数JAVA_POOL_SIZE指定,默认大小为20MB,在运行Java应用程序时,每个类需要4~8KB的Java池空间,这样可以根据Java应用程序中类的个数来估计一下所需的Java池空间。
另外,还可以通过查询动态性能视图v$sgastat来了解Java池的使用情况。例如:
SQL>SELECT * FROM v$sgastat WHERE pool=' java pool' ; [0004]
运行结果如图1-5所示。

图1-5
1.2.5 PGA(程序全局区)
PGA(Program Global Area,程序全局区)是内存中一段特殊的区域,它包含了服务器进程的数据和控制信息,它是一段非共享的内存区域。当服务器进程启动时,数据库服务器为它分配一段PGA,这个PGA只能由当前服务器进程访问。实际上,PGA并不属于实例,而是属于服务器进程私有的。
PGA包括两个部分:私有SQL区和会话内存区。
(1)私有SQL区
在私有SQL区中保存了SQL语句的绑定信息和运行时的内存结构。当用户执行一条SQL语句时,服务器进程即为这条语句分配一段私有SQL区。如果两个用户执行了相同的SQL语句,那么这两段私有SQL区就被映射为一个共享的SQL区。
当用户执行SQL语句时,将显式或隐式地使用游标,每个游标都有一段私有SQL区。私有SQL区由持久区和运行时区组成,其中持久区保存SQL语句的绑定信息,仅当游标关闭时它才被释放。运行时区是在服务器进程接收到SQL语句的执行请求时才产生的,在语句执行结束时被释放。
私有SQL区的位置与会话的连接方式有关。如果会话以专用方式与数据库服务器连接,那么它位于服务器进程的PGA中。如果会话以共享方式连接数据库服务器,那么它将位于SGA中。
(2)会话内存区
会话内存区保存会话变量和其他会话信息。对于共享服务器,这部分内存区是共享的,而不是私有的,这些信息被所有的共享服务器进程所共享。
对于复杂的查询操作,私有SQL区中的运行时区大部分被用作排序、位图的创建、位图的合并等特殊操作,这部分特殊区域叫作“SQL工作区”。例如,用户执行排序操作时,要用到排序区,排序区就位于SQL工作区中。
SQL工作区的大小是可以控制的。这部分内存区域越大,数据库的性能就越高。如果这部分内存区域的大小不足以执行排序等操作,那么将使用临时表空间中的临时段。
在以前的Oracle版本中,用于排序、位图索引的创建等操作的内存区域分别由初始化参数SORT_AREA_SIZE、CREATE_BITMAP_AREA_SIZE、BITMAP_MERGE_AREA_SIZE等指定。在Oracle 11g中,可以对这部分内存区域进行自动管理。首先通过设置初始化参数WORKAREA_SIZE_POLICY,将SQL工作区的管理方式设置为自动方式,然后设置初始化参数PGA_AGGREGATE_TARGET,指定SQL工作区的大小。用户在进行排序等操作时,使用的内存区域的总和不能超过SQL工作区的大小,服务器进程将根据用户操作的需求,自动分配所需的内存区域。
初始化参数WORKAREA_SIZE_POLICY的值有两个:AUTO和MANUAL。如果设置为AUTO,则SQL工作区的管理自动进行,这时就不需要通过SORT_AREA_SIZE等初始化参数为不同的操作分别指定内存区域,只要通过初始化参数PGA_AGGREGATE_TARGET指定PGA的大小即可。如果设置为MANUAL,那么需要分别为用户的各种操作指定所需内存区域的大小。