Oracle 数据库内存结构简介
当实例启动时,Oracle 数据库分配内存区,并启动后台进程。
内存区存储如下信息:
- 程序代码
- 有关每个已连接会话的信息,无论其当前是否处于活动状态
- 在程序执行期间所需的信息,例如,正在索取数据的查询的当前状态
- 在多个进程之间被共享和传递的信息,如锁信息
- 缓存数据,如数据块和重做记录等,它们也存在于磁盘上
基本内存结构
Oracle 数据库包含多个内存区域,每个内存区域包含多个子组件。
与 Oracle 数据库相关联的基本内存结构包括:
- 系统全局区 (SGA)
SGA 是一组称为 SGA 组件的共享内存结构,其中包含一个 Oracle 数据库实例的数据和控制信息。SGA 由所有服务器进程和后台进程共享。例如,SGA 中存储的数据包括数据块缓存和共享 SQL 区。 - 程序全局区(PGA)
PGA 是一个非共享的内存区域,其中包含专门供某个 Oracle 进程使用的数据和控制信息。Oracle 进程启动时,Oracle 数据库会为其创建 PGA。
每个服务器进程和后台进程都存在一个 PGA。所有单个 PGA 的集合即是总实例 PGA,或实例 PGA。数据库初始化参数设置实例 PGA 的大小,而不是单个 PGA 的大小。 - 用户全局区 (UGA)
UGA 是与某个用户会话相关联的内存区。 - 软件代码区
软件代码区是用来存储正在运行或可能要运行的代码的那部分内存。Oracle 数据库代码通常存储在与用户程序不同的位置,——一个更专门或更受保护的位置。
下图说明了这些内存结构之间的关系。
Oracle 数据库内存管理
内存管理涉及维护 Oracle 实例内存结构的最优大小,以满足数据库的变更需求。Oracle 数据库基于与内存相关的初始化参数设置来管理内存。
内存管理的基本选项如下所示:
- 自动内存管理
您指定实例内存的目标大小。数据库实例自动优化到这个目标内存大小,根据需要在 SGA 和 PGA 实例之间重新分配内存。 - 自动共享内存管理
这种管理模式是部分自动化的。您设置一个 SGA 的目标大小,然后设置 PGA 总目标大小,或单独管理 PGA 的各个工作区。 - 手工内存管理
你不必设置总的内存大小,但您需要设置许多初始化参数,以单独管理 SGA 和 PGA 实例中的各个组件。
如果你使用数据库配置助手(DBCA)来创建数据库,并选择了基本安装选项,则将缺省使用自动内存管理。
用户全局区概述
UGA 是为会话变量分配的会话内存,如登录信息和数据库会话所需的其他信息。实际上,UGA 存储了会话状态。
下图描述了 UGA。
如果某个会话将 PL/SQL 包加载到内存,则在 UGA 中包含包状态,即是所有包变量在某个特定的时刻所存储的值集。当包的子程序更改变量的值时,包状态也将更改。默认情况下,包变量在会话的存活期间是唯一且持久的。
OLAP 页面缓冲池也存储在 UGA 中。该池管理相当于数据块的 OLAP 数据页面。页面缓冲池在启动一个 OLAP 会话时分配,并在该会话结束时释放。每当用户查询一个多维对象(如立方体)时,就会自动打开一个 OLAP 会话。
该 UGA 必须在数据库会话的整个存活期间是可用的。由于这个原因,当使用共享服务器的连接时,UGA 不能存储在 PGA 中,因为 PGA 是特定于单个进程的。因此,当使用共享服务器的连接时,UGA 被存储在 SGA 中,以使任何共享服务器进程都能访问它。在使用专用服务器的连接时,UGA 存储在 PGA 中。
程序全局区概述 (PGA)
PGA 是特定于一个操作系统进程或线程的内存区,且不和系统上的其他进程或线程共享。由于 PGA 是特定于进程的,所以它决不会在 SGA 中分配。
PGA 是包含某个专用或共享服务器进程所需的会话变量的内存堆。服务器进程在需要时会在 PGA 中分配内存结构。
PGA 好比是文员所使用的临时工作台面。在这个比喻中,文员是为客户(客户端进程)服务的服务器进程。文员清理出台面的一部分,使用这个工作空间来存储有关客户要求的详细信息,并对顾客请求的文件夹排序,然后在完成工作时让出工作空间。
下图显示某个未配置为共享服务器的实例的 PGA(所有 PGA 的集合)。您可以使用一个初始化参数设置实例 PGA 的目标最大大小。根据需要,各个 PGA 可以按需增大到这个目标大小。
PGA 的内容
PGA 被进一步细分为多个不同区域,每一个都有不同的目的。
下图显示一个专用服务器会话的 PGA 中可能包含的内容。不是所有的 PGA 区域在任何情况下都存在。
私有 SQL 区
私有 SQL 区保存了有关某个已解析的 SQL 语句的信息和其他特定于会话的信息。
当服务器进程执行 SQL 或 PL/SQL 代码时,该过程使用其私有 SQL 区域,来存储绑定变量值、查询执行状态信息、和查询执行工作区。
不要混淆在 UGA 中的私有 SQL 区,和在 SGA 中存储执行计划的共享 SQL 区。在相同或不同的会话中的多个私有 SQL 区,可能指向 SGA 中的一个单一执行计划。例如,在某个会话中运行“SELECT * FROM employees” 20 次,而在另一个不同的会话中运行同一查询 10 次,但它们可以共享相同的执行计划。每次运行的私有 SQL 区并不共享,因此可能包含不同的值和数据。
游标是指向某个特定的私有 SQL 区的一个名称或句柄。如下图所示,你可以将游标看成是一个从客户端指向服务器端状态信息的指针)。游标与私有 SQL 区密切相关,这两个术语有时可以互换使用。
私有 SQL 区又分为以下几个区域:
- run-time 区
此区域包含查询执行状态信息。例如,运行时区域会跟踪到目前为止在全表扫描中检索到的行数。
Oracle 数据库将创建运行时区域,作为一个执行请求的第一步。对于 DML 语句,其运行时区域将在 SQL 语句关闭时被释放。 - 持久区
此区域包含绑定变量的值。绑定变量是执行 SQL 语句时,在运行时提供给 SQL 语句的值。仅当关闭该游标时,持久区域才被释放。
客户端进程负责管理私有 SQL 区。虽然客户端进程可以分配的私有 SQL 区数量由初始化参数 OPEN_CURSORS 限制,但私有 SQL 区的分配和释放主要取决于应用程序。
虽然大多数用户依靠数据库实用程序的游标自动处理机制,但 Oracle 数据库编程接口仍为开发人员提供了对游标更多的控制。一般情况下,应用程序应关闭所有打开的且不再使用的游标,以释放持久区域,并最小化应用程序用户的内存需求。
SQL 工作区
工作区是在 PGA 中为内存密集型操作分配的私有内存区。
例如,排序操作使用排序区来对一组行进行排序。同样,哈希联接操作将其左侧数据为输入,并使用哈希区来创建一个哈希表,而位图合并操作则使用位图合并区来合并从扫描多个位图索引检索到的数据。
如下示例显示了 employees 表和 departments 表相联接的查询计划。
SQL> SELECT *
2 FROM employees e JOIN departments d
3 ON e.department_id=d.department_id
4 ORDER BY last_name;
.
.
.
--------------------------------------------------------------------------------
| Id| Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 106 | 9328 | 7 (29)| 00:00:01 |
| 1 | SORT ORDER BY | | 106 | 9328 | 7 (29)| 00:00:01 |
|*2 | HASH JOIN | | 106 | 9328 | 6 (17)| 00:00:01 |
| 3 | TABLE ACCESS FULL| DEPARTMENTS | 27 | 540 | 2 (0)| 00:00:01 |
| 4 | TABLE ACCESS FULL| EMPLOYEES | 107 | 7276 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------
在上面示例中,运行时区域跟踪全表扫描的进度。此会话在哈希区中执行一个哈希联接,以匹配两个表中的行。而 ORDER BY 排序操作在排序区中进行。
如果要由该运算符处理的数据量太大,不能在工作区中完成,则 Oracle 数据库会将输入数据分成更小的片断。这样一来,数据库先在内存中处理一些数据片断,而将其余数据写入到临时磁盘存储,以待后续处理。
当启用了自动 PGA 内存管理时,数据库自动优化工作区大小。你也可以手动控制和优化工作区大小。更多的信息,请参阅“Memory Management”。
通常,更大的工作区可以显著提高操作性能,但这是以更多的内存消耗为代价的。理想的情况是,工作区的大小足以容纳输入的数据,而由其关联的 SQL 运算符分配辅助内存结构。否则,响应时间会增加,因为输入数据的一部分必须被缓存到磁盘上。在极端情况下,如果工作区的大小相比输入数据的大小显得过小,则数据库必须来回多次倒腾这些数据片断,极大地增大了响应时间。
在专用和共享服务器模式中使用 PGA
PGA 内存分配取决于数据库是使用专用的还是共享的服务器连接。
下表显示了差异之处。
内存区 | 专用服务器 | 共享服务器 |
---|---|---|
会话内存的性质 | 私有的 | 共享的 |
持久区域的位置 | PGA | SGA |
DML/DDL 语句的运行时区域的位置 | PGA | PGA |
系统全局区概述 (SGA)
SGA 是一个可读写的内存区,与 Oracle 后台进程一起组成数据库实例。
所有以用户名义执行的服务器进程,都可以读取 SGA 实例中的信息。在数据库操作过程中,有几个进程会对 SGA 进行写入操作。
每个数据库实例都有其自己的 SGA。当实例启动时 Oracle 数据库自动为其 SGA 分配内存,并在该实例关闭时回收内存。当您使用 SQL*Plus 或 Oracle 企业管理器启动实例时,会显示 SGA 的大小,如下示例所示:
SQL> STARTUP
ORACLE instance started.
Total System Global Area 368283648 bytes
Fixed Size 1300440 bytes
Variable Size 343935016 bytes
Database Buffers 16777216 bytes
Redo Buffers 6270976 bytes
Database mounted.
Database opened.
如图 14-1 所示,SGA 由几个内存组件组成,它们是用来满足特定类别的内存分配请求的内存池。除重做日志缓冲区外,所有 SGA 组件按称为颗粒的连续内存单元来分配和释放空间。颗粒大小是特定于平台的,由 SGA 的总大小决定。
您可以查询 V$SGASTAT 视图,来查看有关 SGA 组件的信息。
最重要的 SGA 组件如下所示:
- Database Buffer Cache
- In-Memory Area
- Redo Log Buffer
- Shared Pool
- Large Pool
- Java Pool
- Fixed SGA
- Optional Performance-Related SGA Subareas
数据库缓冲区高速缓存
数据库缓冲区高速缓存,也称为缓冲区高速缓存,是用于存储从数据文件读取的数据块副本的内存区域。
缓冲区是缓冲区管理器用来暂时缓存当前或最近使用的数据块的主内存地址。所有同时都连接到一个数据库实例的用户,以共享方式访问缓冲区高速缓存。
数据库缓冲区高速缓存的目标
Oracle 数据库使用缓冲区缓存来实现多个目标。
这些目标包括:
- 优化物理 I/O
数据库更新缓存中的数据块,并将有关更改的元数据存储在重做日志缓冲区。提交之后,数据库将重做缓冲区写入磁盘,但不一定会立即将数据块写入磁盘。相反,数据库写入器 (DBWn) 在后台执行惰性写入操作。 - 将频繁访问的块保持在缓冲区高速缓存中,而将不常存取的块写到磁盘
当启用了数据库智能闪存高速缓存 (flash 高速缓存)时,缓冲区高速缓存的一部分可能驻留在闪存缓存中。此缓冲区高速缓存扩展存储在闪存磁盘设备上,这是一种使用闪存的固态存储设备。通过将缓冲区缓存到闪存中,而不是从磁盘读取,数据库可以提高性能。
使用 DB_FLASH_CACHE_FILE 和 DB_FLASH_CACHE_SIZE 初始化参数配置多个 flash 设备。缓冲区缓存跟踪每个设备并将缓冲区均匀地分配到设备。
缓冲区状态
数据库使用内部算法来管理高速缓存中的缓冲区。
缓冲区可以处于下列互斥状态之一:
- unused
缓冲区可供使用,因为它从未使用过,或者当前未使用。这种类型的缓冲区是数据库最容易使用的。 - clean
此缓冲区在之前曾被使用过,而现在包含某个数据块在某个时间点的读取一致版本。块包含数据但是干净的,因此它不需要将执行检查点操作。数据库可以订住该块并重用它。 - dirty
缓冲区包含已修改、但尚未写入到磁盘的数据。数据库在重用该数据块之前必须对其执行检查点操作。
每个缓冲区具有两种访问模式之一:订住的或空闲的 (未订住)。缓冲区被“钉住”在缓存中,以便当其被某个用户会话访问时,它不会因为内存不足被换出内存。多个会话不能在同一时间修改某个已被订住的缓冲区。
缓冲区模式
当客户端请求数据时,Oracle数据库以当前模式或一致模式从数据库缓冲区缓存中检索缓冲区。
模式的区别如下:
- current 模式
当前模式获取,也称为数据库块获取,这是一种对当前已出现在缓冲区高速缓存中的块的检索。例如,如果一个未提交事务已更新某个块中的两行,则当前模式获取会检索这个具有未提交行的块。数据库最常使用数据库块获取的情况是在修改语句期间,它只需更新块的当前版本。 - consistent 模式
一致读取获取是对某个块的一致读取版本的检索。此检索可能会使用撤消数据。例如,如果一个未提交事务已更新某个块中的两行,而在另一个独立会话中的查询请求该块,则数据库使用撤消数据来创建该块的一个读取一致版本(称为一致读取克隆),它不包括未提交的更新。通常,查询以一致模式检索块。
缓冲区 I/O
逻辑 I/O,也称为缓冲区 I/O,指的是读取和写入缓冲区高速缓存中的缓冲区。
当在内存中找不到请求的缓冲区时,数据库将执行一个物理 I/O,将缓冲区从闪存缓存或磁盘复制到内存,然后再执行一个逻辑 I/O,以读取缓存的缓冲区。
缓冲区替换算法
为了使缓冲区访问更有效,数据库必须决定在内存中缓存哪些缓冲区,以及从磁盘访问哪些缓冲区。
数据库使用以下算法:
-
基于 lru 的块级替换算法
这个复杂的算法是默认的,它使用一个包含指向脏缓冲区和非脏缓冲区指针的 least recently used (LRU) 列表。LRU 列表有热端和冷端。冷缓冲区是最近没有使用过的缓冲区。热缓冲区经常被访问,并且最近才被使用。从概念上讲,只有一个 LRU,但是对于数据并发性,数据库实际上使用多个 LRU。 -
基于热度的对象级替换算法
从 Oracle Database 12c Release 1(12.1.0.2) 开始,自动大表缓存特性使表扫描能够在以下场景中使用不同的算法:- 并发查询
在单实例和 Oracle Real Applications Cluster (Oracle RAC) 数据库中,当将 DB_BIG_TABLE_CACHE_PERCENT_TARGET 初始化参数设置为非零值,并将 PARALLEL_DEGREE_POLICY 设置为 auto 或 adaptive 时,并行查询可以使用大表缓存。 - 串行查询
在单实例配置中,当 DB_BIG_TABLE_CACHE_PERCENT_TARGET 初始化参数设置为非零值时,串行查询可以使用大表缓存。
当表不适合放入内存时,数据库根据访问模式决定缓存哪些缓冲区。例如,如果一个受欢迎的表中只有95%适合内存,那么数据库可能会选择将5%的块留在磁盘上,而不是循环地将块读入内存并将块写入磁盘——这种现象称为抖动。当缓存多个大型对象时,数据库会考虑更受欢迎的表更热,而不太受欢迎的表更冷,这将影响缓存哪些块。使用初始化参数 DB_BIG_TABLE_CACHE_PERCENT_TARGET 设置此算法的缓冲区缓存的百分比。
- 并发查询
缓冲区写出
数据库写入器 (DBWn) 进程定期将冷的、脏的缓冲区写入磁盘。
DBWn 在以下情况下会将缓冲区写出:
- 服务器进程找不到干净的缓冲区,以将新块读入数据库缓冲区高速缓存。
随着缓冲区变脏,可用缓冲区的数量就会减少。如果该数值低于一个内部阈值,而又需要干净的缓冲区,则服务器进程将通知 DBWn 执行写出操作。
数据库使用 LRU 来确定哪些脏缓冲区被写出。当脏缓冲区到达 LRU 的冷端时,数据库将其从 LRU 上移出到写出队列。DBWn 将队列中的缓冲区写到磁盘,如果可能使用多块写,则用之。此机制可以防止 LRU 尾端被脏缓冲区塞满,并保证有干净的缓冲区可供重用。 - 数据库必须推进检查点,即重做线程中进行实例恢复的起点。
- 表空间被更改为只读状态,或脱机。
缓冲区读取
当干净的或未使用的缓冲区的数量变得很少时,数据库就必须将某些缓冲区从缓冲区高速缓存删除。
其算法取决于是否启用了闪存缓存:
- 禁用了闪存高速缓存
数据库中根据需要覆盖并重新利用每个干净的缓冲区。如果以后需要重用被覆盖的缓冲区,则数据库必须从磁盘重新读取。 - 启用了闪存高速缓存
DBWn 将干净缓冲区的正文写入到闪存缓存,使其内存中的缓冲区可以被重用。数据库在主内存中保留 LRU 列表的缓冲区头,以跟踪在闪存缓存中的缓冲区体的状态和位置。如果以后需要该缓冲区,则数据库可以从闪存缓存中读取它,而不用从磁盘读取。
当客户端进程请求一个缓冲区时,服务器进程在缓冲区高速缓存中搜索缓冲区。如果数据库在内存中找到缓冲区,则发生缓存命中。搜索顺序如下所示:
- 服务器进程在缓冲区高速缓存中搜索整个缓冲区。
如果该进程发现整个缓冲区,则数据库对此缓冲区执行一个逻辑读取。 - 服务器进程在闪存缓存 LRU 列表中搜索缓冲区头。
如果该进程找到了缓冲区头,则数据库执行一个优化的物理读取,将缓冲区正文从闪存缓存读入到内存内缓存。 - 如果该进程没有在内存中找到该缓冲区(缓存未命中),则服务器进程将执行以下步骤:
- 将该块从数据文件复制到内存中(物理读取)
- 对已读入到内存中的缓冲区执行一个逻辑读取
图 14-6 说明了缓冲区的搜索顺序。扩展的缓冲区高速缓存包括包含整个缓冲区的内存内缓冲区缓存,和包含缓冲区正文的闪存缓存。在图中,数据库在缓冲区高速缓存中搜索某个缓冲区,但未能找到,因此将其从磁盘中读入到内存。
一般情况下,通过缓存命中访问数据比通过缓存未命中要快。缓冲区高速缓存命中率用于测量数据库在缓冲区高速缓存中找到请求的块,而不需要从磁盘中读取的比率。
数据库可能从数据文件或临时文件执行物理读取。在数据文件读取之后,会紧跟着有逻辑 I/O。临时文件读取发生在当内存不足时,迫使数据库将数据写入一个临时表,稍后又将其读回。这种物理读取会绕过缓冲区高速缓存,并不会导致逻辑的 I/O。
缓冲区触摸计数
数据库使用触摸计数来测量对 LRU 列表上的缓冲区进行访问的频率。这种机制使得当某个缓冲区被订住时数据库可以增加一次计数,而不用不断地移动 LRU 列表上的缓冲区。
当缓冲区被订住时,数据库将确定其触摸计数的最后一次递增是何时发生的。如果计数递增发生在超过三秒钟前,则计数将增加 1;否则,计数保持不变。三秒钟规则防止对该缓冲区的一系列突发订住会产生过多计数。例如,一个会话可能会在一个数据块中插入几行,但数据库将这些插入作为一个触摸看待。
如果某个缓冲区是在 LRU 的冷端上,但其触摸计数很高,则该缓冲区将移动到热端。如果触摸计数很低,则缓冲区将从缓存中老化移出。
缓冲区池
缓冲池是缓冲区的集合。
数据库缓冲区高速缓存被划分为一个或多个的缓冲池,它们以几乎相同的方式管理块。对于老化或缓存块,池没有完全不同的算法。
您可以手动配置多个单独的缓冲池,要么将数据保留在缓冲区高速缓存中,或在使用了数据块之后、使缓冲区立即可用于新的数据。然后,你可以将模式对象分配给适当的缓冲池,以控制数据块如何从缓存中老化移出。例如,可以将段分隔为热缓冲池、暖缓冲池和冷缓冲池。
可能的缓冲池如下所示:
- default 池
该池是块通常被缓存的地方。除非您手动配置单独的池,默认池将是唯一的缓冲池。
从 Oracle Database 12c Release 1(12.1.0.2) 开始,大表缓存是默认池中的一个可选部分,它使用基于热度的对象级替换算法。在单实例和 Oracle RAC 数据库中,当 DB_BIG_TABLE_CACHE_PERCENT_TARGET 初始化参数设置为非零值,PARALLEL_DEGREE_POLICY 设置为 auto 或 adaptive 时,并行查询可以使用大表缓存。仅在单实例配置中,当设置 DB_BIG_TABLE_CACHE_PERCENT_TARGET 时,串行查询可以使用大表缓存。 - keep 池
该池用于被频繁访问的块,使其不会由于缺省池的空间不足而被移出。保留缓冲池的目标是将对象保留在内存中,从而避免 I/O 操作。 - recycle
该池用于不被频繁使用的块。循环池防止对象在缓存中占用不必要的空间。
数据库有一个标准的块大小。您可以创建一个块大小不同于标准大小的表空间。每个非默认的块大小都有自己的缓冲池。Oracle 数据库使用与默认池中相同的方式,来管理中这些不同块大小的缓冲池中的块。
下图显示了使用多种缓冲池时的缓冲区高速缓存结构。包含缺省池、保留池和循环池。缺省块大小为 8KB。缓存为各种使用 2KB、4KB 和 16KB 的非标准块大小的表空间包含单独的缓冲池。
缓冲区和全表扫描
数据库使用复杂的算法来管理表扫描。默认情况下,当缓冲区必须从磁盘读入时, 数据库会将缓冲区插入到 LRU 列表的中部。通过这种方式,热块可以保留在缓存中,以使他们不需要再次从磁盘读取。
全表扫描顺序读取表高水位(HWM)下的所有行,这可能引发一个问题。假设表段中块的总大小大于缓冲区高速缓存的大小。在此表上的全表扫描可能会清除高速缓存,致使数据库不能将频繁访问的块维持在高速缓存中。
全表扫描的默认模式
默认情况下,数据库对全表扫描采用一种保守的方法,仅当表大小占缓冲区缓存的一小部分时才将小表加载到内存中。
为了确定是否应该缓存中等大小的表,数据库使用了一种算法,该算法包含最后一次表扫描之间的时间间隔、缓冲区缓存的老化时间戳和缓冲区缓存中剩余的空间。
对于非常大的表,数据库通常使用直接读取路径,该路径直接将块加载到 PGA 并完全绕过 SGA,以避免填充缓冲区缓存。对于中等大小的表,数据库可以使用直接读取或缓存读取。如果决定使用缓存读取,则数据库将这些块放在 LRU 列表的末尾,以防止扫描有效地清除缓存。
从 Oracle Database 12c Release 1(12.1.0.2) 开始,数据库实例的缓冲区缓存自动执行内部计算,以确定内存是否足够将数据库完全缓存到实例 SGA 中,以及访问时缓存表是否有利于性能。如果整个数据库可以完全装入内存,并且满足其他各种内部条件,那么 Oracle 数据库将数据库中的所有表都视为小表,并认为它们有资格进行缓存。但是,数据库不缓存标记为 NOCACHE 属性的 lob。
并发查询执行
在执行全表扫描时,数据库有时可以通过使用多个并行执行服务器来提高响应时间。
在某些情况下,当数据库有大量内存时,数据库可以在系统全局区域(SGA)中缓存并行查询数据,而不是使用直接路径读取程序全局区域(PGA)。通常,由于潜在的资源使用,并行查询发生在低并发性数据仓库中。
缓存属性
在不需要默认缓存行为的罕见情况下,可以使用 ALTER TABLE … CACHE 更改将大表中的块读入数据库缓冲区缓存的方式。
对于具有缓存属性集的表,数据库不会强制或固定缓冲区缓存中的块。相反,数据库以与任何其他表块相同的方式将块从缓存中取出。在执行此选项时要小心,因为对大表的完整扫描可能会清除缓存中的大多数其他块。
KEEP 属性
对于大型表,可以使用 ALTER TABLE…STORAGE BUFFER_POOL KEEP 使扫描将这些表的块加载到 KEEP 池中。
将表放入 KEEP 池将更改存储块的缓冲区缓存的部分。数据库不是将块缓存到默认缓冲池中,而是将它们缓存到 KEEP 缓冲池中。没有单独的算法控制 KEEP 池缓存。
强制使用全数据库缓存模式
为了提高某些情况下的性能,可以显式地执行 ALTER DATABASE … FORCE FULL DATABASE CACHING 语句,以启用强制全数据库缓存模式。
与默认模式(自动模式)相反,强制全数据库缓存模式认为整个数据库(包括 NOCACHE lob)都适合在数据库缓冲区缓存中进行缓存。从 Oracle Database 12c Release 1(12.1.0.2) 开始,可以使用这种模式。
Oracle 建议,只有当每个实例的缓冲区缓存大小大于数据库大小时,才启用强制全数据库缓存模式。该准则适用于单实例和 Oracle RAC 数据库。但是,当 Oracle RAC 应用程序被很好地分区时,当所有实例的组合缓存大于数据库大小时,您可以启用强制的全数据库缓存模式,并且有额外的空间来处理实例之间的重复缓存块。
重做日志缓冲区
重做日志缓冲区是 SGA 中的一个循环式缓冲区,用来存储对数据库所做更改的重做条目。
重做记录是一种数据结构,它包含用于重建(或重做)由 DML 或 DDL 操作对数据库所做更改所需的信息。数据库恢复将重做条目应用到数据文件,以重建丢失的更改。
Oracle 数据库进程将重做条目从用户内存空间复制到 SGA 的重做日志缓冲区中。重做条目占用缓冲区中连续、顺序的空间。日志写入器(LGWR)后台进程将重做日志缓冲区写入磁盘上的活动联机重做日志组。图 14-8 显示了这种重做缓冲区活动。
LGWR 进程将重做信息顺序写入磁盘,而 DBWn 进程将数据块分散写入磁盘。分散写入往往要比顺序写入慢得多。LGWR 使用户能够避免等待 DBWn 完成其缓慢的写入,为数据库提供了更好的性能。
LOG_BUFFER 初始化参数指定了 Oracle 数据库在缓冲重做条目时所能使用的内存量。与其他的 SGA 组件不同,重做日志缓冲区和固定的 SGA 缓冲区不按颗粒划分内存。
共享池
共享池缓存各种类型的程序数据。
例如,共享池存储已解析的 SQL、PL/SQL 代码、系统参数、和数据字典信息。几乎数据库中发生的每个操作都涉及到共享池。例如,如果用户执行一个 SQL 语句,则 Oracle 数据库会访问共享池。
共享池分为几个子组件,其中最重要的几个显示在图 14-9 中。
库缓存
库缓存是存储可执行 SQL 和 PL/SQL 代码的共享池内存结构。
此缓存包含共享 SQL 和 PL/SQL 区,以及锁和库缓存句柄之类的控制结构。在共享服务器体系结构中,库缓存还包含私有 SQL 区。
在执行 SQL 语句时,数据库将尝试重用以前执行过的代码。如果在库缓存中存在该 SQL 语句的已解析表示形式,并且是可以共享的,则数据库会重用该代码,这称为软解析或库缓存命中。否则,数据库必须为应用程序代码建立一个新的可执行版本,这称为硬解析或库缓存未命中。
共享 SQL 区
数据库所运行的每个 SQL 语句,会存在于共享 SQL 区和私有 SQL 区。
数据库使用共享 SQL 区来处理 SQL 语句第一次发生时的情况。该区域对所有用户可访问,并包含语句的解析树和执行计划。对于每个唯一的语句,只存在一个共享 SQL 区。每个发出 SQL 语句的会话在其 PGA 中有一个私有 SQL 区。提交同一语句中的每个用户都分别有一个私有 SQL 区,但都指向同一共享 SQL 区。因此,在多个单独 PGA 中的私有 SQL 区可能与同一共享 SQL 区相关联。
当应用程序提交类似的 SQL 语句时,数据库会自动决定怎么做。数据库会同时考虑从用户和应用程序直接发出的语句,及其它语句从内部发出的递归 SQL 语句。
数据库执行以下步骤:
-
检查共享池,看是否存在某个语法和语义上都相同的语句的共享 SQL 区:
- 如果存在这样一个相同的语句,则数据库为该语句的后续新实例使用这个共享 SQL 区,从而减少内存消耗。
- 如果不存在这样一个相同的语句,则数据库在共享池中分配一个新的共享 SQL 区。语法相同、但语义不同的语句会使用一个子游标。
在这两种情况下,用户的私有 SQL 区都指向包含语句和执行计划的共享 SQL 区。
-
为该会话分配一个私有 SQL 区
私有 SQL 区的位置取决于该会话所建立的连接。如果会话是通过共享服务器连接的,则该私有 SQL 区中的一部分被保存在 SGA 中。
图 14-10 显示了一个专用服务器架构,其中两个会话在其 PGA 中保留了同一 SQL 语句的副本。若在共享服务器中,此副本会位于大池的 UGA 中,或当大池不存在时位于共享池中。
程序单元和库缓存
库缓存包含 PL/SQL 程序和 Java 类的可执行形式。这些项目统称为程序单元。
数据库程序单元的处理与 SQL 语句类似。例如,数据库分配一个共享区域来保存 PL/SQL 程序的已解析和已编译形式。数据库分配一个私有区域来保存运行该程序特定于会话的值,包括为执行 SQL 的本地、全局、包变量、和缓冲区等。如果有多个用户运行同一程序,则每个用户将维护其私有 SQL 区的一个单独副本,其中包含特定于会话的值,以访问某个单一的共享 SQL 区。
数据库按前面所述的那样处理 PL/SQL 程序单元内的单个 SQL 语句。尽管它们源于同一 PL/SQL 程序单元,这些 SQL 语句将使用一个共享区域来保存其已分析的表示形式,并为运行该语句的每个会话使用一个私有区域。
共享池中的内存分配和重用
当解析新的 SQL 语句时,数据库将分配共享池内存。内存大小取决于语句的复杂性。
通常,共享池中的项目会一直保留,直到按 LRU 算法将其移除。数据库允许共享池中用于多个会话的项目被保留在内存中,只要他们还有用,即使创建该项目的进程已经终止。这一机制能最小化开销和对 SQL 语句的处理。如果需要为新项目腾出空间,则数据库会释放内存中不常使用的项目。
ALTER SYSTEM FLUSH SHARED_POOL 语句删除共享池中的所有信息,以及更改全局数据库名称。
数据字典缓存
数据字典是数据库表和视图的集合,其中包含有关数据库及其结构、用户等参考信息。
Oracle 数据库在解析 SQL 语句期间,会频繁访问数据字典。Oracle 数据库如此频繁地访问数据字典,所以指定了以下这些特别的内存位置来保存字典数据:
- 数据字典缓存
此缓存保存有关数据库对象的信息。此缓存也称为行缓存,因为它按行、而不是按缓冲区保存数据。 - 库缓存
所有服务器进程都共享这些缓存来对数据字典信息进行访问。
服务器结果缓存
与缓冲池保存数据块不同,服务器结果缓存保存的是结果集。
服务器结果缓存包含 SQL 查询结果缓存和 PL/SQL 函数结果缓存,它们共享相同的基础结构。
SQL 查询结果缓存
SQL查询结果高速缓存是存储查询和查询片段的服务器结果缓存的子集。
大多数应用程序都从这种性能改进中获益。考虑一个重复运行相同 SELECT 语句的应用程序。如果结果会被缓存,则数据库会将其立即返回。通过这种方式,数据库避免了重读块和重计算结果等昂贵操作。
执行查询时,数据库搜索内存以确定结果是否存在于结果缓存中。如果结果存在,那么数据库将从内存中检索结果,而不是执行查询。如果结果没有缓存,那么数据库执行查询,将结果作为输出返回,然后将结果存储在结果缓存中。每当事务修改了数据或用于构造该缓存结果的数据库对象元数据时,数据库自动使其高速缓存结果无效。
用户可以为查询或查询片断添加 RESULT_CACHE 提示,以指示数据库应将其结果存储在 SQL 查询结果缓存中。由 RESULT_CACHE_MODE 初始化参数确定 SQL 查询结果缓存将用于所有查询(如果可能)还是仅用于添加了注释的查询。
PL/SQL 函数结果缓存
PL/SQL函数结果缓存是存储函数结果集的服务器结果缓存的子集。
若不使用缓存功能,每次调用花 1 秒钟的函数,调用该函数 1000 次将花时间 1000 秒。而缓存后,以相同的输入调用函数 1000 次总共仅需要时间 1 秒。对于在相对静态的数据上被频繁调用的函数,结果缓存是一个很好的选择。
PL/SQL 函数代码可以包括一个缓存其结果的请求。调用此函数时,系统将检查缓存。如果高速缓存中包含之前使用相同参数值调用该函数的结果缓存,则系统将缓存结果返回给调用程序,而不会重新执行函数体。如果缓存中不包含其结果,则系统执行函数体,并在将控制返回给调用程序前,将其(针对这些参数值的)结果添加到缓存中。
缓存中可能会积累很多的结果缓存 ——当调用每个结果缓存函数时,每个不同的参数组合都形成一个结果缓存。如果数据库需要更多内存,则它会移除一个或多个缓存结果。
保留池
保留池是共享池中的一个内存区,Oracle 数据库使用它来分配大的连续内存块。
数据库以块的形式从共享池分配内存。大块使得大型对象(超过 5KB)可以被加载到缓存中,而不需要一个单一的连续区域。这样一来,数据库中由于碎片引起的连续内存不足的可能性降低了。
有时,Java、PL/SQL 或 SQL 游标也可能会从共享池中分配大于 5KB 的内存块。为使这种分配更有效,数据库从共享池中为保留池隔离出少量内存。
大池
大池是一个可选的内存区域,它所提供的内存分配往往比共享池分配的内存更大。
大池可以提供以下大内存分配:
- 共享服务器的 UGA 和 Oracle XA 接口(用于与多个数据库进行交互的事务)
- 并行执行中使用的消息缓冲区
- 恢复管理器 (RMAN) I/O 从属进程的缓冲区
- 用于延迟插入的缓冲区(使用 MEMOPTIMIZE_WRITE 提示插入)
下图是大池的图形显示方式
大池内存管理
大池管理内存的方式与共享池不同,共享池使用 LRU 列表,以便部分内存可以老化退出。
大池没有 LRU 列表。当数据库将大池内存分配给数据库会话时,除非会话释放该内存,否则不能释放该内存。一旦释放了一部分内存,其他进程就可以使用它。通过从大池中分配会话内存,数据库避免了共享池中可能出现的碎片。
用于延迟插入的大池缓冲区
对于一种称为延迟插入的特殊插入类型,数据库从大池中分配缓冲区。
对于来自物联网(IoT)应用程序的快速“fire and forget”插入,数据库基础设施会造成性能开销。例如,代码路径包括缓冲区缓存导航、缓冲区引脚和并发保护。数组插入将这些成本最小化,但是数组必须构建在客户端,这对于物联网应用程序来说并不常见。要解决这个问题,Oracle 应用程序可以使用提示将行插入到指定为 MEMOPTIMIZE FOR WRITE 的表中。
插入被延迟,因为它们被缓冲在大池中,然后稍后由后台进程异步地写入磁盘。数据库处理延迟插入的过程如下:
- 应用程序将 MEMOPTIMIZE_WRITE 插入发送到中间层,中间层可以聚合数据。虽然物联网应用程序几乎总是将插入发送到中间层,但也可以直接将插入发送到数据库。例如,使用 SQL*Plus 将插入直接发送到数据库。
- 中间层将插入的聚合写入数据库服务器。
- 中间层客户机可以选择保留上一步中编写的数据的本地副本。
- 服务器进程将数据写入一个或多个大池中的缓冲区。
为了避免争用,每个缓冲区都有自己的内部锁定机制。这种锁定机制与数据库缓冲区缓存用于其缓冲区的锁定机制是分开的。基本的编写过程如下:- 实例启动后,第一个 MEMOPTIMIZE_WRITE 插入从大池中分配缓冲区。
- 写入器从可用缓冲区列表中选择一个缓冲区。
- 如果选择的缓冲区没有被锁定,并且这个缓冲区有空闲空间,那么客户端就会向缓冲区写入数据,并使用特定于会话的序列号标记每个缓冲区的写入。如果没有,则写入器返回到前面的步骤,并以这种方式继续,直到在大池中找到缓冲区或释放了足够的空间。
- 数据库从缓冲数据创建一个服务器端数组。
- 空间管理协调器(SMCO)及其助手进程(Wnnn)使用标准数据块格式异步地将数组写入磁盘。
与标准插入不同,延迟插入是自动提交的,不能回滚。数据库按照插入在会话中出现的顺序将插入提交给给定对象。无法保证对象或会话之间的顺序。
与常规插入一样,数据库支持约束和索引维护。但是,数据库在写到磁盘的过程中执行计算,而不是在写到大池的过程中。
下图描述了延迟插入的工作流。
Java 池
Java 池是一个存储 Java 虚拟机 (JVM) 内所有会话特定的 Java 代码和数据的内存区。此内存包括在调用端迁移到 Java 会话空间的 Java 对象。
对于专用服务器连接,Java 池包括每个 Java 类的共享部分,包括方法和只读内存,如代码向量,但不包括每个会话的 Java 状态。对于共享服务器,Java 池包括每个 Java 类的共享部分,和用于每个会话状态的一些 UGA 。每个 UGA 根据需要增长和缩小,但总的 UGA 大小必须适应 Java 池空间。
Java 池顾问统计提供有关用于 Java 的库缓存内存信息,并预测 Java 池大小的更改如何影响解析率。当 statistics_level 设置为 TYPICAL 或更高时,Java 池顾问在内部会被打开。这些统计信息在顾问被关闭时会被重置。
固定 SGA
固定 SGA 是内部的内务管理区域。
例如,固定 SGA 包含:
- 有关数据库及其实例状态的一般信息,后台进程需要访问这些信息
- 进程间通讯的信息,如有关锁的信息
固定 SGA 的大小由 Oracle 数据库设置,且不能手动更改。固定 SGA 大小可能会因为版本不同而不同。
可选的与性能相关的 SGA 子区域
一些 SGA 子区域只支持特定的性能特性。
In-Memory 区
In-Memory 区是一个可选的 SGA 组件,它包含内存中的列存储(IM 列存储)。
IM 列存储包含表、分区和物化视图的副本,其列格式经过优化,适合快速扫描。IM 列存储补充了数据库缓冲区缓存,后者以传统的行格式存储数据。
Memoptimize 池
memoptimize 池为堆组织的表存储缓冲区和相关结构,这些表指定为 MEMOPTIMIZE FOR READ。
这种结构为基于键的查询提供了高性能和可伸缩性,比如 SELECT * FROM cust WHERE cid = 10。为了减少端到端响应时间,客户机通过网络直接从 SGA 中提取请求的缓冲区,从而避免 CPU 和操作系统开销。应用程序可以从 memoptimize 池中获益,而不需要更改代码。
memoptimize 池包含两部分:
- Memoptimize 缓冲区
为了避免磁盘 I/O,数据库永久锁定 memoptimize 池中 MEMOPTIMIZE FOR READ 表的缓冲区,直到表被标记为NO MEMOPTIMIZE FOR READ。memoptimize 缓冲区使用与数据库缓冲区缓存中的缓冲区相同的结构。但是,memoptimize 池中的缓冲区完全独立于数据库缓冲区缓存,并且不计入其大小。memoptimize 缓冲区占memoptimize 池的 75%。 - Hash 索引
哈希索引是一种非持久性的段数据结构。数据库将哈希索引分配为多个非连续的内存单元。每个单元包含许多散列桶。一个单独的映射结构将一个内存单元与一个主键关联起来。哈希索引占 memoptimize 池的 25%。
要启用 memoptimize 池,请将 MEMOPTIMIZE_POOL_SIZE 初始化参数设置为整数值(默认情况下该池被禁用)。该值指定要分配给池的SGA数量。MEMOPTIMIZE_POOL_SIZE 值不计入SGA_TARGET,但是数据库不会自动增长和收缩 memoptimize 池。例如,如果SGA_TARGET是 10 GB,如果 MEMOPTIMIZE_POOL_SIZE 是 1GB,那么除 memoptimize 池外,SGA内存总共有 9 GB 可用。
要更改 memoptimize 池的大小,必须手动设置 MEMOPTIMIZE_POOL_SIZE 并重新启动数据库实例。不能使用 ALTER SYSTEM 动态更改池大小。
DBMS_MEMOPTIMIZE 包允许显式地将表填充到 memoptimize 池中。
软件代码区概述
软件代码区是用于存储正在运行或可以运行的代码的那部分内存。Oracle 数据库代码的存储位置通常比用户程序更专用、更受保护。
软件区的大小通常是静态的,其大小只在软件更新或重新安装时才会改变。这些区域所需的大小因操作系统而异。
软件区是只读的,并且可以按共享或非共享方式安装。某些数据库工具,如 Oracle Forms 和 SQL*Plus,可以共享安装,但有些却不能。只要可能,这些数据库代码会被共享,以便所有用户都可以访问它,而无需在内存中存在多个副本,从而减少主内存并改进整体性能。数据库的多个实例可以与同一台计算机上运行的不同的数据库使用相同的数据库代码区域。