14. 内存架构
14.1. Oracle是数据库内存结构简介
当实例启动时,Oracle数据库会分配一个内存区域并启动后台进程。这个内存区域存储以下信息:
- 程序代码
- 每个已连接会话的信息,即使它当前未活跃
- 程序执行期间需要的信息,例如,正在从中提取行的查询的当前状态
- 锁数据等在进程间共享和通信的信息
- 缓存的数据,如数据块和重做记录,这些数据也存在于磁盘上
14.1.1. 基础内存结构
与Oracle数据库相关联的基本内存结构包括:
-
系统全局区(SGA)
SGA是一组共享内存结构,称为SGA组件,包含一个Oracle数据库实例的数据和控制信息。SGA被所有服务器和后台进程共享。存储在SGA中的数据示例包括缓存的数据块和共享SQL区域。 -
程序全局区(PGA)
PGA是一个非共享内存区域,包含专门供一个Oracle进程使用的数据和控制信息。当启动一个Oracle进程时,Oracle数据库会创建PGA。每个服务器进程和后台进程都有一个PGA。各个PGA的集合构成了整个实例PGA,或称为实例PGA。数据库初始化参数设置了实例PGA的大小,而不是单个PGA的大小。 -
用户全局区(UGA)
UGA是与用户会话相关联的内存。 -
软件代码区域
软件代码区域是用于存储正在运行或可以运行的代码的内存部分。Oracle数据库代码存储在通常与用户程序不同的位置——一个更专属或受保护的位置。
图14-1说明了这些内存结构之间的关系。
14.1.2. Oracle数据库内存管理
内存管理涉及在数据库需求变化时维护Oracle实例内存结构的最优大小。Oracle数据库根据内存相关初始化参数的设置来管理内存。内存管理的基本选项如下:
-
自动内存管理
您指定实例内存的目标大小。数据库实例自动调整到目标内存大小,在SGA和实例PGA之间按需重新分配内存。 -
自动共享内存管理
这种管理模式是部分自动化的。您为SGA设置一个目标大小,然后可以选择为PGA设置一个总体目标大小,或者单独管理PGA工作区。 -
手动内存管理
您不是设置总内存大小,而是设置许多初始化参数来单独管理SGA和实例PGA的各个组件。
如果您使用数据库配置助手(DBCA)创建数据库并选择基本安装选项,则自动内存管理是默认设置。
14.2. 用户全局区概述
UGA是会话内存,它是为会话变量分配的内存,如登录信息以及数据库会话所需的其他信息。本质上,UGA存储了会话状态。图14-2描述了UGA。
如果一个会话将一个PL/SQL包加载到内存中,那么UGA包含包的状态,这是在特定时间存储在所有包变量中的值的集合(参见第8-6页的“PL/SQL包”)。当包子程序更改变量时,包状态会发生变化。默认情况下,包变量是唯一的,并且会持续存在于会话的生命周期内。
OLAP页面池也存储在UGA中。这个池管理OLAP数据页面,这些页面等同于数据块。页面池在OLAP会话开始时分配,在会话结束时释放。当用户查询维度对象(如立方体)时,OLAP会话会自动打开。
UGA必须在会话的生命周期内对数据库会话可用。因此,当使用共享服务器连接时,UGA不能存储在PGA中,因为PGA特定于单个进程。因此,在使用共享服务器连接时,UGA存储在SGA中,这使得任何共享服务器进程都可以访问它。当使用专用服务器连接时,UGA存储在PGA中。
14.3. 程序全局区概述
PGA是特定于一个操作系统进程或线程的内存,系统上的其他进程或线程不共享。因为PGA是进程特定的,所以它从不在SGA中分配。
PGA是一个内存堆,包含专用或共享服务器进程所需的会话依赖变量。服务器进程在PGA中分配它所需要的内存结构。
PGA的一个比喻是一个文件管理员使用的临时柜台工作区。在这个比喻中,文件管理员是代表客户(客户端进程)工作的服务器进程。管理员清理柜台的一部分,使用工作区来存储有关客户请求的详细信息,并按客户要求对文件夹进行排序,然后在工作完成后放弃这个空间。图14-3显示了一个未配置为共享服务器的实例的实例PGA(所有PGA的集合)。您可以使用初始化参数来设置实例PGA的目标最大大小(参见第18-17页的“内存管理方法摘要”)。各个PGA可以根据需要增长,直到达到这个目标大小。
注意:后台进程也会分配自己的PGA。这里的讨论仅关注服务器进程的PGA。
14.3.1. PGA内容
PGA被细分为不同的区域,每个区域都有不同的目的。图14-4显示了一个专用服务器会话的PGA可能包含的内容。并非所有情况下都会存在PGA的所有区域。
14.3.1.1. 私有SQL区
一个私有SQL区域保存了关于解析后的SQL语句和其他会话特定信息的处理。当服务器进程执行SQL或PL/SQL代码时,该进程使用私有SQL区域来存储绑定变量值、查询执行状态信息和查询执行工作区。不要将位于UGA中的私有SQL区域与位于SGA中存储执行计划的共享SQL区域混淆。同一个或不同会话中的多个私有SQL区域可以指向SGA中的单个执行计划。例如,一个会话中执行20次SELECT * FROM employees
和另一个会话中执行10次相同查询可以共享同一个计划。每次执行的私有SQL区域是不共享的,并且可能包含不同的值和数据。游标是指向特定私有SQL区域的名称或句柄。如图14-5所示,您可以将游标视为客户端的指针和服务器端的状态。由于游标与私有SQL区域密切相关,这两个术语有时可以互换使用。
一个私有SQL区域被划分为以下区域:
-
运行时区域
这个区域包含查询执行状态信息。例如,运行时区域跟踪在全表扫描中到目前为止检索的行数。
Oracle数据库在执行请求的第一步创建运行时区域。对于DML语句,运行时区域在SQL语句关闭时释放。 -
持久区域
这个区域包含绑定变量值。绑定变量值在执行SQL语句时在运行时提供。只有当游标关闭时,持久区域才被释放。
客户端进程负责管理私有SQL区域。私有SQL区域的分配和释放在很大程度上取决于应用程序,尽管客户端进程可以分配的私有SQL区域数量受到初始化参数OPEN_CURSORS的限制。
尽管大多数用户依赖数据库实用程序的自动游标处理,但Oracle数据库的程序化接口为开发人员提供了对游标更多的控制。通常,应用程序应该关闭所有不再使用的打开的游标,以释放持久区域,并最小化应用程序用户所需的内存。
14.3.1.2. SQL工作区
工作区是PGA内存的私有分配,用于内存密集型操作。例如,排序操作符使用排序区域对一组行进行排序。同样,哈希连接操作符使用哈希区域从其左侧输入构建哈希表,而位图合并使用位图合并区域来合并从多个位图索引扫描检索到的数据。
示例14-1显示了员工和部门的连接及其查询计划。
在示例14-1中,运行时区域跟踪全表扫描的进度。会话在哈希区域执行哈希连接以匹配两个表中的行。ORDER BY排序发生在排序区域。
如果操作符要处理的数据量不适合工作区,则Oracle数据库将输入数据分成较小的片段。通过这种方式,数据库在内存中处理一些数据片段,同时将其余部分写入临时磁盘存储,以便以后处理。
当启用自动PGA内存管理时,数据库会自动调整工作区大小。您也可以手动控制和调整工作区的大小。有关更多信息,请参见第18-15页的“内存管理”。通常,较大的工作区可以显著提高操作符的性能,代价是更高的内存消耗。理想情况下,工作区的大小足以容纳输入数据和其关联SQL操作符分配的辅助内存结构。如果不是这样,响应时间会增加,因为部分输入数据必须缓存在磁盘上。在极端情况下,如果工作区的大小与输入数据的大小相比太小,则数据库必须对数据片段执行多次遍历,显著增加响应时间。
14.3.2. 专用服务器模式与共享服务器模式下的PGA的使用
PGA内存分配取决于数据库是使用专用还是共享服务器连接。表14-1显示了它们之间的差异。
14.4. 系统全局区概述
SGA是一个读写内存区域,与Oracle后台进程一起构成数据库实例。所有代表用户执行的服务器进程都可以读取实例SGA中的信息。在数据库操作期间,有多个进程会向SGA写入数据。
注意:服务器和后台进程并不位于SGA内,而是存在于一个单独的内存空间中。
每个数据库实例都有自己的SGA。Oracle数据库在实例启动时自动分配SGA内存,并在实例关闭时回收内存。当您通过SQL*Plus或Oracle Enterprise Manager启动一个实例时,SGA的大小会显示如下例所示:
如图14-1所示,SGA由多个内存组件组成,这些内存池用于满足特定类别的内存分配请求。除了重做日志缓冲区之外,所有SGA组件都以称为颗粒的连续内存单元来分配和释放空间。颗粒大小是平台特定的,由总SGA大小决定。您可以查询V$SGASTAT视图以获取有关SGA组件的信息。最重要的SGA组件包括:
- 数据库缓冲区高速缓存
- 重做日志缓冲区
- 共享池
- 大池(Large Pool)
- Java池(Java Pool)
- Streams池(Streams Pool)
- 固定SGA(Fixed SGA)
14.4.1. 数据库缓冲区高速缓存
数据库缓冲区高速缓存,也称为缓冲区高速缓存,是存储从数据文件中读取的数据块副本的内存区域。缓冲区是主内存地址,缓冲区管理器在其中临时缓存当前或最近使用的数据块。所有同时连接到数据库实例的用户共享对缓冲区高速缓存的访问。
Oracle数据库使用缓冲区高速缓存来实现以下目标:
-
优化物理I/O:数据库在缓存中更新数据块,并将更改的元数据存储在重做日志缓冲区中。提交事务后,数据库将重做缓冲区写入磁盘,但不会立即将数据块写入磁盘。相反,数据库写入器(DBW)在后台执行延迟写入。
-
保留频繁访问的数据块在缓冲区高速缓存中,并将不常访问的数据块写入磁盘
当启用数据库智能闪存高速缓存(闪存高速缓存)时,缓冲区高速缓存的一部分可以存储在闪存高速缓存中。这种缓冲区高速缓存扩展存储在闪存磁盘设备上,这是一种使用闪存的固态存储设备。数据库可以通过在闪存中缓存缓冲区而不是从磁性磁盘读取来提高性能。
注意:数据库智能闪存高速缓存仅在Solaris和Oracle企业Linux中可用。
14.4.1.1. 缓冲区状态
数据库使用内部算法来管理缓存中的缓冲区。一个缓冲区可以处于以下互斥状态之一:
-
未使用
缓冲区可供使用,因为它从未被使用过或当前未使用。这种类型的缓冲区对数据库来说使用起来最简单。 -
干净
缓冲区之前被使用过,现在包含一个块在某个时间点的一致性读版本。该块包含数据,但是是“干净”的,因此不需要检查点。数据库可以固定该块并重用它。 -
脏
缓冲区包含尚未写入磁盘的修改数据。数据库必须在重用之前对该块进行检查点。
每个缓冲区都有一个访问模式:固定或自由(未固定)。一个缓冲区在缓存中被“固定”,以便在用户会话访问它时不会从内存中老化出去。多个会话不能同时修改一个固定的缓冲区。
数据库使用一个复杂的算法来使缓冲区访问高效。脏缓冲区和非脏缓冲区的指针存在于同一个最近最少使用(LRU)列表上,该列表有一个热端和冷端。冷缓冲区是最近未被使用的缓冲区。热缓冲区是经常被访问并且最近被使用的缓冲区。
注意:概念上只有一个LRU列表,但为了并发,数据库实际上使用了几个LRU列表。
14.4.1.2. 缓冲模式
当客户端请求数据时,Oracle 数据库以以下任一模式从数据库缓冲区缓存中检索缓冲区:
■ 当前模式
当前模式获取,也称为 db 块获取,是检索缓冲区缓存中当前块的状态。例如,如果一个未提交的事务更新了一个块中的两行,那么当前模式获取将检索包含这些未提交行的块。数据库在修改语句期间最频繁地使用 db 块获取,这些语句必须仅更新块的当前版本。
■ 一致性模式
一致性读取获取是检索块的一致性版本。这种检索可能会使用 undo 数据。例如,如果一个未提交的事务更新了一个块中的两行,并且如果一个单独会话中的查询请求该块,那么数据库将使用 undo 数据创建一个不包含未提交更新的块的一致性版本(称为一致性读取克隆)。通常,查询以一致性模式检索块。
14.4.1.3. 缓冲I/O
逻辑 I/O,也称为缓冲区 I/O,指的是对缓冲区缓存中缓冲区的读写操作。当请求的缓冲区在内存中未找到时,数据库执行物理 I/O 将缓冲区从闪存缓存或磁盘复制到内存中,然后执行逻辑 I/O 来读取缓存的缓冲区。
缓冲区写入:数据库写入进程(DBWn)在以下情况下定期将冷的、脏的缓冲区写入磁盘:
■ 服务器进程在数据库缓冲区缓存中找不到干净的缓冲区来读取新的块。
随着缓冲区变脏,空闲缓冲区的数量减少。如果空闲缓冲区的数量低于内部设定的阈值,并且需要干净的缓冲区,服务器进程会通知 DBWn 进行写入。
数据库利用最近最少使用(LRU)算法来决定哪些脏缓冲区需要被写入。当脏缓冲区到达 LRU 列表的冷端时,数据库会将它们从 LRU 列表中移出并放入写队列。DBWn 将写队列中的缓冲区写入磁盘,如果可能的话,会使用多块写入以提高效率。这种机制防止 LRU 列表的末端因脏缓冲区过多而阻塞,并确保可以找到干净的缓冲区以便再次使用。
■ 数据库需要推进检查点位置,检查点是重做日志中必须从该位置开始进行实例恢复的点。
■ 表空间被设置为只读状态或被置于离线状态。
缓冲区读取:当干净或未使用的缓冲区数量较低时,数据库必须从缓冲区缓存中移除缓冲区。算法取决于是否启用了闪存缓存:
■ 闪存缓存未启用
数据库根据需要重用每个干净的缓冲区,覆盖它。如果稍后需要被覆盖的缓冲区,则数据库必须从磁盘读取它。
■ 闪存缓存已启用
DBWn 可以将干净缓冲区的主体写入闪存缓存,从而允许重用其内存中的缓冲区。数据库将缓冲区头部保留在主内存中的 LRU 列表中,以跟踪闪存缓存中缓冲区主体的状态和位置。如果稍后需要此缓冲区,则数据库可以从闪存缓存而不是从磁盘读取它。
当客户端进程请求缓冲区时,服务器进程会在缓冲区缓存中搜索该缓冲区。如果数据库在内存中找到缓冲区,则会发生缓存命中。搜索顺序如下:
- 服务器进程在缓冲区缓存中搜索整个缓冲区。
如果进程找到整个缓冲区,则数据库对此缓冲区执行逻辑读取。 - 服务器进程在闪存缓存 LRU 列表中搜索缓冲区头部。如果进程找到缓冲区头部,则数据库从闪存缓存到内存缓存中执行缓冲区主体的优化物理读取。
- 如果进程在内存中找不到缓冲区(缓存未命中),则服务器进程执行以下步骤:
a. 将块从数据文件复制到内存(物理读取)
b. 对已读取到内存中的缓冲区执行逻辑读取
图 14-6说明了缓冲区搜索顺序。扩展的缓冲区缓存包括内存中的缓冲区缓存(包含整个缓冲区)和闪存缓存(包含缓冲区主体)。在图中,数据库在缓冲区缓存中搜索缓冲区,未找到缓冲区,然后从磁盘读取到内存。
通常,通过缓存命中访问数据比通过缓存未命中要快。缓冲区缓存命中率衡量了数据库在不需要从磁盘读取的情况下在缓冲区缓存中找到请求块的频率。
数据库可以从数据文件或临时文件执行物理读取。从数据文件的读取之后会跟随逻辑 I/O。从临时文件的读取发生在内存不足时,数据库将数据写入临时表并在之后读取。这些物理读取绕过了缓冲区缓存,并且不会发生逻辑 I/O。
缓冲区访问次数:数据库使用触摸计数来衡量 LRU 列表中缓冲区的访问频率。这种机制允许数据库在缓冲区被固定时增加计数器,而不是不断地在 LRU 列表上重新排列缓冲区。
注意:数据库不会在内存中物理移动块。移动是列表上指针位置的变化。
当一个缓冲区被固定时,数据库会确定它的触摸计数最后一次增加是什么时候。如果计数是在三秒前增加的,那么计数会增加;否则,计数保持不变。三秒规则防止了对缓冲区的一系列固定被计为多次触摸。例如,一个会话可能在一个数据块中插入多行,但数据库将这些插入视为一次触摸。
如果一个缓冲区位于 LRU 的冷端,但其触摸计数很高,那么缓冲区会移动到热端。如果触摸计数很低,那么缓冲区就会从缓存中老化出去。
缓冲区和全表扫描:当缓冲区必须从磁盘读取时,数据库将缓冲区插入到 LRU 列表的中间。这样,热块可以保留在缓存中,因此不需要再次从磁盘读取。
全表扫描提出了一个问题,它会顺序读取表高水位线下的所有行(见第 12-27 页的“段空间和高水位线”)。假设一个表段中的块的总大小大于缓冲区缓存的大小。对这个表的完整扫描可能会清空缓冲区缓存,阻止数据库维护频繁访问块的缓存。
作为对大表进行全扫描的结果而读入数据库缓存的块,与其他类型的读取不同。这些块立即可供重用,以防止扫描有效地清空缓冲区缓存。
在默认行为不期望的罕见情况下,您可以更改表的 CACHE 属性。在这种情况下,数据库不会强制或固定缓冲区缓存中的块,而是像任何其他块一样让它们从缓存中老化出去。使用此选项时要小心,因为对大表的全扫描可能会清除缓存中的大部分其他块。
14.4.1.4. 缓冲池
缓冲池是一组缓冲区的集合。数据库缓冲区缓存被划分为一个或多个缓冲池。
您可以手动配置单独的缓冲池,这些缓冲池要么在缓冲区缓存中保留数据,要么在用完数据块后立即使缓冲区可用于新数据。然后,您可以将特定的模式对象分配给适当的缓冲池,以控制块如何从缓存中老化出去。
可能的缓冲池如下:
■ 默认池
这个池是通常缓存块的位置。除非您手动配置单独的池,否则默认池是唯一的缓冲池。
■ 保留池
这个池是为那些频繁访问但因空间不足而从默认池中老化出去的块设计的。保留缓冲池的目标是在内存中保留对象,从而避免 I/O 操作。
■ 回收池
这个池是为那些不经常使用的块设计的。回收池可以防止对象在缓存中占用不必要的空间。
数据库有一个标准块大小(见第 12-7 页的“数据库块大小”)。您可以创建一个块大小与标准大小不同的表空间。每个非标准块大小都有自己的池。Oracle 数据库以与默认池相同的方式管理这些池中的块。
图 14-7 显示了使用多个池时缓冲区缓存的结构。缓存包含默认、保留和回收池。默认块大小为 8 KB。缓存包含使用非标准块大小 2 KB、4 KB 和 16 KB 的表空间的单独池。