4.2 实例的内存结构
当实例启动时,系统为实例分配了一段内存空间,并启动若干后台进程。内存空间分成不同的部分,分别用来存储不同的信息。具体来说,在这段内存空间中存储以下信息:
·程序代码:Oracle的可执行代码。
·缓冲数据:用户要访问的数据、重做日志等。这部分内存叫做SGA。
·与会话有关的信息。
·与进程间通信有关的信息,如加锁的信息。
在上述内存区域中,最重要的是SGA。SGA是由多个缓存和缓冲池组成的,在这些内存结构中存储不同类型的数据。根据存储数据的类型,SGA中主要包含以下类型的内存结构:
·数据库高速缓存
·重做日志缓冲区
·共享池
·Java池
·大池
其中数据库高速缓存由许多缓冲区组成,共享池由数据字典缓存和库缓存两部分组成。在这里之所以使用了缓存和缓冲区两个概念,是因为它们来自不同的英文单词,缓存是从单词cache翻译来的,而缓冲区来自单词buffer。
当实例运行时,可以通过命令查看SGA的大小。SGA是由不同的内存结构组成的,所以查看的结果是分别列出了不同组成部分的大小。例如,在SQL*Plus中执行下面的SHOW命令可以查看当前SGA的大小:
SQL>SHOW SGA
Total System Global Area 135338868 bytes
Fixed Size 453492 bytes
Variable Size 109051904 bytes
Database Buffers 25165824 bytes
Redo Buffers 667648 bytes
SGA的更详细的信息可以从动态性能视图v$sga、v$sgainfo和v$sgastat中获得。从这些视图中可以获得每种缓冲区和缓存的大小信息。
4.2.1 数据库高速缓存
数据库高速缓存是SGA中的一段存储区域,用来存放用户最近访问的数据。当用户访问数据文件中的数据时,服务器进程首先查看这样的数据是否已经存在于数据库高速缓存中。如果是,则直接在数据库高速缓存对数据进行访问,并将处理结果返回给用户,这次数据访问叫做“命中”,这样的读操作称为“逻辑读”。否则,服务器进程将数据从数据文件的数据块中读到数据库高速缓存中,然后在数据库高速缓存对数据进行访问,这次数据访问叫做“未命中”,这样的读操作称为“物理读”。显然,如果直接在数据库高速缓存访问数据,要比从数据文件中读数据快得多。所以,访问数据的命中率越高,数据库的性能就越高。对数据库进行性能优化的一个重要方面就是提高逻辑读在所有读操作中所占的比例。
数据库高速缓存的大小通过初始化参数DB_CACHE_SIZE来指定。提高数据访问命中率的最直接方法是增加数据库高速缓存的大小,但它的大小不能无限制地增加,它要受到物理内存大小的限制。
用户访问的数据都存储在数据文件中,数据文件被划分为许多大小相同的数据块。数据块是Oracle进行读写的基本单位,也就是说,即使用户只希望访问一个字节的数据,那么数据所在的整个数据块也将被读到数据高速缓存中。同样,在写数据时,也是以数据块为单位的。这样做的好处是提高了数据库服务器的吞吐量。
数据库高速缓存是由一个个的缓冲区组成的,数据从数据文件中被读到数据库高速缓存中之后,就放在这些缓冲区中。缓冲区的大小与数据块的大小一致,一个数据块的内容恰好放在一个缓冲区中。数据块的大小由初始化参数DB_BLOCK_SIZE指定,并且在数据库创建后不能被修改,那么缓冲区的大小也由这个参数决定。所有缓冲区大小的总和由初始化参数DB_CACHE_SIZE指定。假设在参数文件中定义了以下两个初始化参数:
DB_BLOCK_SIZE=8192
DB_CACHE_SIZE=25165824
这说明每个缓冲区的大小为8192字节,即8KB,而所有缓冲区的总的大小为25165824字节,即24MB,这也是整个数据库高速缓存的大小。由此可以计算组成整个数据库高速缓存的缓冲区个数,即25 165 824/8192=3072个。
前面已经提到,提高数据访问命中率的一个有效方法是增加数据库高速缓存的大小。但是由于受到物理内存的限制,数据库高速缓存不可能无限大。如果将数据库高速缓存设置得过大,操作系统可以使用的内存将减少,这样就要使用虚拟内存,反而会降低系统性能。所以数据访问的命中率不可能达到100%。如果用户访问的数据不在数据库高速缓存中,就需要把数据读到某个缓冲区中。一个数据块的内容到底放在哪个缓冲区中,如果没有足够的空闲缓冲区该怎么办呢?我们有必要分析一下缓冲区的使用情况。
根据缓冲区的使用情况,我们可以把缓冲区分为空闲缓冲区、脏缓冲区和忙缓冲区三种类型。
如果一个缓冲区中存放的是由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语句,这个参数可以设置得小一些。但是在一个数据库中,可能会对一部分数据主要执行SELECT语句,对另外一部分数据主要执行DML语句。在这种情况下,我们可以在数据库中定义不同的数据块大小,根据数据的不同访问要求,把它们放在不同的数据块中,从而从整体上提高数据库的性能。
由初始化参数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_SIZE=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
db_block_size integer 8192
下面的命令将显示16kB缓冲区的大小:
SQL>SHOW PARAMETER DB_16K_CACHE_SIZE
NAME TYPE VALUE
db_16k_cache_size big integer 58720256