文章
数据仓储
作者:Murali Vallath
了解该特性对数据不会频繁改变的数据库(如数据仓库或报表数据库环境)的好处。
2011 年 1 月发布
Oracle Real Application Clusters (RAC) 是一个集群数据库解决方案,为业务统一体提供可伸缩性和可用性。“可伸缩性”是一种相对说法,基于这样一种简单的规则:随着访问系统的用户数量增加,RAC 配置应能够应对这种用户数量增加。然而,重要的是,仅当应用程序在单实例 Oracle 环境中能够伸缩时,它在集群环境中才能够伸缩 — 如果应用程序是在 CPU 数量从 2 增加到 4、6、8 的情况下在一台服务器上进行伸缩的,将无法在 RAC 环境中伸缩。基本上,Oracle RAC 无法上演修复性能低下的应用程序代码的奇迹。(相比之下,可用性是系统在一个或多个组件发生故障时提供连续服务的能力。)
在可伸缩的系统中,负载必须透明地分布在集群中的所有节点上,实现真正负载平衡的环境。为了实现此目标,Oracle RAC 架构允许通过互连在实例间移动缓存的数据,否则若需要使用物理 I/O,代价将十分高昂。
例如,Oracle 数据库的结果缓存特性是在 Oracle Database 11g 第 1 版中引入的,该特性因其在内存中缓存 SQL 查询和 PL/SQL 函数结果的能力而备受关注。因此,在 Oracle RAC 环境中,相同查询或函数的多次执行可直接由缓存中的一个数据集处理,而无需在每次需要时通过互连移动数据集。在本文中,您将了解该特性的工作原理。
缓存:Oracle 提供了多种类型和形式的缓存:库缓存、缓冲区缓存、字典缓存、数据库缓存、保留缓存、回收缓存等。通过缓存数据来提高性能是 Oracle 架构长久以来的目标。
初次执行一项查询时,用户进程将在数据库缓冲区缓存中搜索数据。如果找到了数据(因为以前有人检索过此数据),则使用此数据;否则将执行 I/O 操作,将磁盘上数据文件中的数据检索到缓冲区缓存中,生成最终结果集。
随后,如果另一个查询需要相同的数据集,该进程将使用来自缓冲区缓存的数据生成用户所需的结果集。 如果缓冲区缓存包含可重用的数据,那么这个新增的结果缓存是什么?简而言之,结果缓存可以被称作缓存(在本文中是一个共享池)中的缓存区。因此,结果缓存是共享池中的一个区域,包含查询执行的最终结果。
这意味着什么?让我们通过一个示例来加深了解;以下查询是在 Oracle Database 11g 第 2 版数据库中执行的:
SELECT OL_NUMBER, SUM (OL_AMOUNT), SUM (OL_QUANTITY) FROM ORDER_LINE OL, ORDERS ORD WHERE OL.OL_O_ID = ORD.O_ID AND OL.OL_W_ID = ORD.O_W_ID AND OL.OL_D_ID = ORD.O_D_ID GROUP BY OL_NUMBER;
跟踪文件的 tkprof 报告的输出显示,查询遍历了超过 347,000 行,提供了包含 300 行摘要数据的最终输出,即结果集。
call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.01 0.02 0 0 0 0 Execute 1 0.00 0.00 0 0 0 0 Fetch 21 33.04 96.12 346671 347172 0 300 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 23 33.06 96.14 346671 347172 0 300
如果另一名用户希望执行完全相同的查询,情况又会如何?用户会话将不得不再次遍历缓冲区缓存中的所有这些行,获得 300 行的最终结果。如果有一种方法能够在第二次和以后的每一次直接获取这 300 行,应该怎么办?只要我们有足够的缓冲区来容纳这些数据,就能每次直接获取这些行。
这就是结果缓存的用处。如图 1 所示,利用结果缓存特性,这 300 行被移至(第 3 步)共享池的结果缓存部分中。随后,在执行相同的查询时,可以在结果缓存中查找结果,而无需遍历 347,000 行数据。这难道不是一个巧妙的特性吗?

图 1 结果缓存行为
可以在客户端或服务器端对结果缓存进行管理。客户端结果缓存实现需要应用程序使用 Oracle 调用接口 (OCI) 调用。相比之下,服务器端实现就简单得多。就本文的讨论而言,我们将侧重于结果缓存的服务器端实现。
对于服务器端实现,相同的查询可使用 /*+ RESULT CACHE */ 提示执行,也可以将 result_cache_mode 参数设置为 AUTO。将此参数设置为 AUTO 会将所有查询结果移至共享池的结果缓存部分中,这样最终的 300 行将移至共享池的结果缓存部分中以便重用。
在执行带有此提示的查询时,数据库会在共享池的结果缓存部分中查找,确定缓存中是否存在结果。如果确实存在,数据库将检索结果并将数据返回给用户,而不执行查询。
然而,可以由于以下原因而不缓存结果:
初次执行查询。
由于 result_cache_max_result 设定的限制,其他操作需要使用所分配的空间,因而缓存已刷新。
一个数据库管理员执行了 dbms_result_cache.flush 过程,查询必须再次执行。随后,将最终结果集移动并存储到结果缓存中。
NAME TYPE VALUE ------------------------------------ ----------- ---------- client_result_cache_lag big integer 3000 client_result_cache_size big integer 0 result_cache_max_result integer 5 result_cache_max_size big integer 251680K result_cache_mode string MANUAL result_cache_remote_expiration integer 0
client_result_cache_lag 和 client_result_cache_size 参数用于在客户端配置结果缓存。其他参数用于在服务器端配置结果缓存。
服务器上的结果缓存大小由两个参数决定:result_cache_max_result 和 result_cache_max_size。
默认情况下,result_cache_max_size 参数约为 memory_target 参数的 0.25%,或者 shared_pool 参数的 1%。可对此参数进行修改,以控制结果缓存中存储的内容量。result_cache_max_result 参数指定一个结果缓存可占用 result_cache_max_size 的百分比情况。默认值为 5%。
每个结果集在缓存中都是使用 CACHE_ID 标识的,这是一个长度为 90 个字符的字符串。查询的 CACHE_ID 与 V$SQL 中包含的用于标识库缓存中的查询的 SQL_ID 并不匹配。与针对 Oracle 数据库执行的每个 SQL 查询生成的 SQL_ID 不同,CACHE_ID 针对的是共享池的结果缓存部分中存储查询最终结果的一个区域或存储段 (bucket)。
如果上述查询在执行时使用了 /*+ RESULT_CACHE */ 提示,将生成以下计划。分配给结果缓存的名称是 CACHE_ID。
--------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 150 | 4950 | | 126K (1)| 00:25:17 |
| 1 | RESULT CACHE | 8fbjhchhd9zwh7uhn4mv7dhvga | | | | | |
| 2 | HASH GROUP BY | | 150 | 4950 | | 126K (1)| 00:25:17 |
|* 3 | HASH JOIN | | 20M| 649M| 45M| 125K (1)| 00:25:09 |
| 4 | INDEX FAST FULL SCAN| ORDERS_I2 | 2063K| 21M| | 2743 (1)| 00:00:33 |
| 5 | INDEX FAST FULL SCAN| IORDL | 20M| 432M| | 87415 (1)| 00:17:29 |
--------------------------------------------------------------------------------------------------------------
有几个视图用于监视与结果缓存相关的信息。与结果缓存相关的对象可从 V$RESULT_CAHCE_OBJECTS 视图获取。以下查询可帮助验证结果缓存中包含 CACHE_ID 为 8fbjhchhd9zwh7uhn4mv7dhvga 的结果集。
SQL> SELECT ID, TYPE, CREATION_TIMESTAMP, BLOCK_COUNT, COLUMN_COUNT, PIN_COUNT, ROW_COUNT FROM V$RESULT_CACHE_OBJECTS WHERE CACHE_ID='8fbjhchhd9zwh7uhn4mv7dhvga'; ID TYPE CREATION_ BLOCK_COUNT COLUMN_COUNT PIN_COUNT ROW_COUNT ---------- ---------- --------- ----------- ------------ ---------- ---------- 2 Result 19-JAN-10 1 3 0 300
本文的主要目的是讨论 Oracle RAC 环境中的结果缓存行为。因此,让我们来更具体地了解结果缓存,逐步深入讨论其在 Oracle RAC 实现中的行为。对于 Oracle RAC 新手,我们首先从一些背景信息入手。
对于新接触集群环境的人来说,Oracle RAS 是这样一项技术:两个或多个共享相同物理数据库的 Oracle 关系数据库管理系统 (RDBMS) 实例。多个用户可以通过参与集群的任何实例访问数据(即,数据的相同实例),他们可以查看数据的准确副本。

图 2 Oracle RAC 配置
如图 2 所示,以下是 Oracle RAC 实现相对于单实例配置特有的特征:
许多 Oracle Database 实例在许多节点上运行。通过实现 Oracle Database 11g 第 2 版中提供的特性(如服务器池),不再像之前版本中那样对实例到服务器/节点的亲和性作硬性要求,从而为基于集群环境中的负载动态供应实例提供了机遇。
多个实例共享一个物理数据库。
所有实例具有通用的数据和控制文件。无论集群中有多少实例,只有一组数据文件和控制文件。
每个实例都有自己的日志文件和还原段。
所有实例可同时对一个物理数据库执行事务。
参与集群配置的实例利用缓存融合技术通过集群互连进行通信。
Oracle RAC 跨集群互连在实例间维护缓存同步。
每个节点都包含一个虚拟 IP 地址,供连接到数据库的用户会话使用。
集群维护一个 SCAN 地址,该允许用户动态连接到负载最低的任意服务器。(SCAN 地址类似于 OpenVMS 和 Tru64 操作系统中的集群别名特性。这些地址是智能的,知道集群中的哪些服务器的负载最低,从而将新连接放在负载最低的服务器上。有关 SCAN 工作原理的讨论不在本文档的讨论范围内。)
每个节点都包含一个 Oracle Automatic Storage Manager 实例,提供对存储基础架构的访问并提供选项,例如在数据库得到有效利用时添加和删除存储。
所有参与的节点利用 Oracle Clusterware 彼此协调,实现成员可用性和表决。
用户查询数据库中的数据时,将从存储子系统检索数据并加载到缓冲区缓存中,遍历数据直至提取出最终结果集。然后将最终数据集发送给用户。之后,如果其他用户执行相同的查询,将从缓冲区缓存中读取数据,并将数据结果集返回给用户。数据并非始终可用。如果缓冲区空间不足或者另一个会话修改了数据,则要求刷新并重新加载缓冲区中的数据。
在 Oracle RAC 环境中,用户从不同实例执行查询时,不再是使用每次都必须从 I/O 子系统检索数据的 DBWR 进程,而是(通常)通过一个实例到另一个实例的互连传输数据。(在 Oracle Database 11g 第 2 版中,缓存合并技术内使用的新增的“bypass reader”算法会在读取大量行时跳过数据传输,转为使用来自请求实例的本地 I/O 子系统检索数据。)这能提供可观的性能收益,因为从 I/O 子系统检索数据的延迟与通过网络传输数据相比要高得多。基本上,网络延迟比 I/O 延迟要低得多。
数据传输给请求实例之后,执行计划就会遍历行,提取用户请求的实际结果集。
有多种类型的数据访问模式可以在 Oracle RAC 环境中实现,具体取决于应用程序和数据库的类型,例如:
方法 1:普通查询执行 — 如果针对较小的数据子集进行查询,例如在 OLTP 实现中,将从存储中本地访问数据。
方法 2:并行查询执行 — 如果针对较大的数据集进行查询,例如在数据仓库实现中,应考虑使用并行执行并利用多个实例中可用的资源。
方法 3:结果缓存 — 如果查询执行分析函数或者是一个汇总表,应考虑利用 Oracle 11g 中的结果缓存特性,其最终结果将存储在结果缓存中,可供系统中的其他用户使用。
让我们利用之前使用过的查询来进行演示。在 Oracle Database 11g 第 2 版中,这些选项中的每一个都用不同的方法检索数据,在某些情况下,这些方法与之前版本的 Oracle Database 中有所不同。
为了全面理解 Oracle RAC 环境中结果缓存特性的行为,先讨论前两种方法然后再深入讨论结果缓存一定会很有意思。
在一个四节点集群中(图 2),在实例 1 (SSKY1) 中执行查询时,如 10046 跟踪输出所示,通过对 ORDERS 和 ORDER_LINE 这两个表进行索引快速全扫描来执行查询。该操作从磁盘(物理 I/O)读取 345,361 个数据块,并执行另外 345,447 个逻辑 I/O 操作。最终结果集为 300 行,随后将发回给用户。
注意:10046 跟踪可使用 - alter session set events '10046 trace name context forever, level 12' 启用,可使用 - alter session set events '10046 trace name context off' 禁用。将在参数 user_dump_dest 定义的位置生成跟踪输出。
SELECT OL_W_ID, OL_D_ID, OL_NUMBER, sum(OL_AMOUNT),sum(OL_QUANTITY)
FROM
ORDER_LINE OL, ORDERS ORD WHERE OL.OL_O_ID = ORD.O_ID AND OL.OL_W_ID =
ORD.O_W_ID AND OL.OL_D_ID = ORD.O_D_ID GROUP BY OL_NUMBER, OL_W_ID,OL_D_ID
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.01 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 21 18.42 77.41 345361 345447 0 300
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 23 18.43 77.42 345361 345447 0 300
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 89 (schema name)
Rows Row Source Operation
------- ---------------------------------------------------
300 HASH GROUP BY (cr=345447 pr=345361 pw=0 time=149 us cost=126413 size=4950 card=150)
21349787 HASH JOIN (cr=345447 pr=345361 pw=0 time=59061480 us cost=125703 size=680920944 card=20633968)
2134685 INDEX FAST FULL SCAN ORDERS_I2 (cr=11248 pr=11219 pw=0 time=258291 us cost=2743 size=22694870 card=2063170)(object id 86234)
21349787 INDEX FAST FULL SCAN IORDL (cr=334199 pr=334142 pw=0 time=47799580 us cost=87415 size=453947296 card=20633968)(object id 86202)
如果此查询是在实例 2 (SSKY2) 上执行的,将再次执行完整的操作(执行计划完全相同),包括物理 I/O 和逻辑操作在内,之后将获得 300 行的完整结果。
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.01 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 21 31.74 74.34 345361 345447 0 300
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 23 31.76 74.36 345361 345447 0 300
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 89 (schema name)
Rows Row Source Operation
------- ---------------------------------------------------
300 HASH GROUP BY (cr=345447 pr=345361 pw=0 time=299 us cost=126413 size=4950 card=150)
21349787 HASH JOIN (cr=345447 pr=345361 pw=0 time=62985040 us cost=125703 size=680920944 card=20633968)
2134685 INDEX FAST FULL SCAN ORDERS_I2 (cr=11248 pr=11219 pw=0 time=490345 us cost=2743 size=22694870 card=2063170)(object id 86234)
21349787 INDEX FAST FULL SCAN IORDL (cr=334199 pr=334142 pw=0 time=42913972 us cost=87415 size=453947296 card=20633968)(object id 86202)
分析来自两个实例的跟踪输出和下面列出的等待事件,我们注意到,不存在数据的缓存同步。如前所述,Oracle Database 11g 第 2 版 RAC 中的情况是全新的(不同于 Oracle Database 10g),本地 I/O 操作(bypass reader 算法)将更有益,因为避免了通过互连传输数据。
实例 1 (SSKY1) |
| 实例 2 (SSKY2) |
|
其上等待的事件 | 等待 | 其上等待的事件 | 等待 |
-------------------------------- | 时间 | ------------------------------ | 时间 |
ges message buffer allocation | 466 | ges message buffer allocation | 135 |
library cache lock | 2 | library cache pin | 2 |
row cache lock | 14 | row cache lock | 18 |
SQL*Net message to client | 21 | SQL*Net message to client | 21 |
Disk file operations I/O | 3 | Disk file operations I/O | 1 |
os thread startup | 1 | os thread startup | 1 |
KSV master wait | 2 | KSV master wait | 2 |
ASM file metadata operation | 1 | ASM file metadata operation | 1 |
db file sequential read | 6 | db file sequential read | 6 |
db file parallel read | 1 | db file parallel read | 96 |
db file scattered read | 2762 | db file scattered read | 2667 |
asynch descriptor resize | 1 | asynch descriptor resize | 1 |
gc current grant busy | 206 |
|
|
gc cr block 2-way | 50 | gc cr block 2-way | 21 |
SQL*Net message from client | 21 | SQL*Net message from client | 21 |
|
| gc cr multi block request | 112 |
|
| gc cr block 3-way | 22 |
边栏:比较 11g 第 2 版和 10g 第 2 版
实例 1: 10g 第 2 版数据库中查询生成的执行计划表明,该查询执行了约 214,682 次磁盘 I/O 操作和 42,907,485 次逻辑 I/O 操作才生成 300 行的结果集。 call count cpu elapsed disk query current rows Misses in library cache during parse: 1 Rows Row Source Operation
在第二个实例上执行查询的执行计划表明,尽管逻辑 I/O 操作保持不变,但几乎没有磁盘 I/O。那么数据是如何进入数据库缓冲区缓存的?如下面的等待事件统计信息所示,这是利用缓存融合技术在专用互连上完成的。 call count cpu elapsed disk query current rows Misses in library cache during parse: 1 Rows Row Source Operation
将此应用于上述讨论,块不在实例 2(请求者)的缓冲区缓存内。然而,由于之前有一个用户在实例 1(持有方)上执行过此查询,块必须通过互连传递给实例 2。 注意:如果块当前不在本地实例(请求者)的缓冲区缓存内但在另一个实例(持有者)中,将发生 gc current block 3-way 等待事件,但块保留在第三个实例上,必须先执行三跳操作,请求实例才能接收到块。无论集群中有多少实例,这都是请求者在接收到块之前可发生的最大跳数。 对比 Oracle Database 11g 第 2 版与 Oracle Database 10g 第 2 版之间的逻辑 I/O 操作,显然 Oracle Database 10g 第 2 版中的逻辑 I/O 数量比 Oracle Database 11g 第 2 版中高得多。这是 Oracle Database 11g 第 2 版数据库优化器整合了改进的结果。 |
如果启用了并行操作,则上述缓存同步的整体行为都会发生变化。Oracle Database 11g 第 2 版引入了几个新参数。我们要重点讨论的参数是 PARALLEL_DEGREE_POLICY。该参数的默认值为 MANUAL。将它更改为 AUTO 将导致 Oracle RAC 跨多个实例产生从属进程,在可能的情况下无需并行查询提示即执行此查询。在一个或多个实例上产生的从属进程的数量是自动的,取决于资源的可用性。
另一个需要注意的参数是 PARALLEL_DEGREE_LIMIT。该参数的值可以是 I/O、CPU 或者一个指定最大并行度的整数值。
让我们在将并行度策略设置为 AUTO 的情况下再次尝试这些查询。
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- --------- ---------- ----------
Parse 11 0.01 0.02 0 0 0 0
Execute 11 19.17 86.67 181238 190696 0 0
Fetch 21 0.03 8.34 0 0 0 300
------- ------ -------- ---------- ---------- -------- ---------- ----------
total 43 19.23 95.04 181238 190696 0 300
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 89 (schema name) (recursive depth: 1)
Rows Row Source Operation
------- ---------------------------------------------------
0 PX COORDINATOR (cr=0 pr=0 pw=0 time=0 us)
0 PX SEND QC (RANDOM) :TQ10003 (cr=0 pr=0 pw=0 time=0 us cost=10094 size=4950 card=150)
26 HASH GROUP BY (cr=0 pr=0 pw=0 time=0 us cost=10094 size=4950 card=150)
260 PX RECEIVE (cr=0 pr=0 pw=0 time=345 us cost=10094 size=4950 card=150)
0 PX SEND HASH :TQ10002 (cr=0 pr=0 pw=0 time=0 us cost=10094 size=4950 card=150)
0 HASH GROUP BY (cr=0 pr=0 pw=0 time=0 us cost=10094 size=4950 card=150)
0 HASH JOIN (cr=0 pr=0 pw=0 time=0 us cost=10025 size=680920944 card=20633968)
0 PX RECEIVE (cr=0 pr=0 pw=0 time=0 us cost=305 size=22694870 card=2063170)
0 PX SEND HASH :TQ10000 (cr=0 pr=0 pw=0 time=0 us cost=305 size=22694870 card=2063170)
247592 PX BLOCK ITERATOR (cr=1986 pr=1378 pw=0 time=82701 us cost=305 size=22694870 card=2063170)
247592 INDEX FAST FULL SCAN ORDERS_I2 (cr=1986 pr=1378 pw=0 time=38108 us cost=305 size=22694870 card=2063170)(object id 86234)
0 PX RECEIVE (cr=0 pr=0 pw=0 time=0 us cost=9713 size=453947296 card=20633968)
0 PX SEND HASH :TQ10001 (cr=0 pr=0 pw=0 time=0 us cost=9713 size=453947296 card=20633968)
2153776 PX BLOCK ITERATOR (cr=36697 pr=35311 pw=0 time=4484449 us cost=9713 size=453947296 card=20633968)
2153776 INDEX FAST FULL SCAN IORDL (cr=36697 pr=35311 pw=0 time=4067280 us cost=9713 size=453947296 card=20633968)(object id 86202)
在这样的并行操作中,查询协调器识别集群中的各实例产生的从属进程。然后,每个从属进程都会检索数据子集,将其重新发回启动操作的实例以进行整合,最终向用户显示结果集。
值得注意的是,使用并行选项执行查询时,使用缓存融合技术通过互连将数据移至请求实例(也就是说,不使用新的 bypass reader 算法)。这是因为,与前述方法 1 的操作相比,检索和收集的数据数量更少。
通过以下部分查询输出,应注意到,PX 协调器进程会在参与集群操作的所有节点上产生从属进程,以便完成查询执行。
set linesize 140 col NAME FORMAT A28 col VALUE FORMAT 9999999999 break on inst_id on qcsid on server_set SELECT stat.inst_id,stat.qcsid, stat.server_set, stat.server#, nam.name, stat.val ue FROM gv$px_sesstat stat, gv$statname nam WHERE stat.inst_id = nam.inst_id AND stat.statistic# = nam.statistic# AND nam.name = 'physical reads' ORDER BY 1,2,3; Ins ID QCSID SERVER_SET SERVER# NAME VALUE --- ---------- ---------- ---------- ---------------------------- ----------- 1 76 1 1 physical reads 0 physical reads 0 1083 1 8 physical reads 1452 9 physical reads 1300 7 physical reads 1348 2 8 physical reads 24832 7 physical reads 24832 9 physical reads 24448 physical reads 151 2 76 1 2 physical reads 0 1083 1 5 physical reads 1226 6 physical reads 1328 4 physical reads 1368 2 4 physical reads 29921 5 physical reads 29176 6 physical reads 29920 …………………………
注意:有关并行处理的更多背景信息,请参见此白皮书。
结果缓存有何不同?我们之前讨论过,结果缓存提供了极大的优势,因为查询的最终结果存储在共享池的一个单独部分中,之后,当用户执行相同的查询时,进程不必遍历缓冲区缓存中的数百万行,而是可以跳过这一步,从共享池的结果缓存部分中检索数据。
在 Oracle RAC 环境中,如果从一个实例多次执行查询,过程并无不同;将从结果缓存中检索结果。那么,有什么区别吗?实际上,可以说有,也可以说没有。区别体现在集群中的第二个实例使用 /*+ RESULT CACHE */ 提示执行相同的查询时。不是从 I/O 子系统获取所有行(如我们在方法 1 中看到的那样),而是仅传输来自结果缓存的结果。
在 Oracle RAC 环境中,这是一项极大的优势,可减少互连流量或 I/O 子系统调用。那么,文档中为什么说结果缓存是实例本地的?因为它确实如此。在 Oracle RAC 环境中,不存在全局结果缓存;结果缓存在实例的共享池内本地维护。(参见图 3。)
我们来通过一次练习逐个步骤地讨论 Oracle RAC 环境中的这项特性。
第 1 步
让我们来看看,为结果缓存部分分配了多少缓存,目前有多少缓存可用。可使用以下方法确定共享池的结果缓存部分的当前使用率:
SQL>SET SERVEROUTPUT ON; SQL>execute DBMS_RESULT_CACHE.MEMORY_REPORT(TRUE); R e s u l t C a c h e M e m o r y R e p o r t [Parameters] Block Size = 1K bytes Maximum Cache Size = 251680K bytes (251680 blocks) Maximum Result Size = 12584K bytes (12584 blocks) [Memory] Total Memory = 12784 bytes [0.000% of the Shared Pool] ... Fixed Memory = 12784 bytes [0.000% of the Shared Pool] ....... Memory Mgr = 200 bytes ....... Bloom Fltr = 2K bytes ....... = 2088 bytes ....... Cache Mgr = 5552 bytes ....... State Objs = 2896 bytes ... Dynamic Memory = 0 bytes [0.000% of the Shared Pool]
共享池的结果缓存部分还可通过对 v$sgastat 视图进行查询来验证。
SQL> SELECT * FROM gv$sgastat WHERE POOL='shared pool' AND NAME LIKE 'Result%' AND INST_ID =1;
INST_ID POOL NAME BYTES
------- ----------- ---------------------------------- ----------
1 shared pool Result Cache: State Objs 2896
1 shared pool Result Cache: Memory Mgr 200
1 shared pool Result Cache: 2088
1 shared pool Result Cache: Cache Mgr 5552
1 shared pool Result Cache: Bloom Fltr 2048
上面的输出和之前内存报告生成的输出表明了相同的统计信息。
在上面的输出中,我们注意到,没有为结果缓存部分分配共享池的任何内存(0.000% 的共享池)。结果缓存是从共享池的动态内存部分分配的。
我们还可以使用以下查询间接验证是否出现了任何对象。该查询将列出结果缓存中当前存储的所有对象。
SQL> SELECT INST_ID INT, ID, TYPE, CREATION_TIMESTAMP, BLOCK_COUNT, COLUMN_COUNT,
PIN_COUNT, ROW_COUNT FROM GV$RESULT_CACHE_OBJECTS;
第 2 步
让我们在实例 1 (SSKY1) 上使用 /*+ RESULT CACHE */ 提示执行查询。
SELECT /*+ RESULT_CACHE */ OL_W_ID, OL_D_ID, OL_NUMBER, sum(OL_AMOUNT),sum(OL_QUANTITY)
FROM
ORDER_LINE OL, ORDERS ORD WHERE OL.OL_O_ID = ORD.O_ID AND OL.OL_W_ID =
ORD.O_W_ID AND OL.OL_D_ID = ORD.O_D_ID GROUP BY OL_NUMBER, OL_W_ID, OL_D_ID
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.02 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 21 33.85 97.00 346671 346755 0 300
------- ------ -------- -------------------- ---------- ---------- ----------
total 23 33.86 97.03 346671 346755 0 300
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 89 (TPCC)
Rows Row Source Operation
------- ---------------------------------------------------
300 RESULT CACHE 8fbjhchhd9zwh7uhn4mv7dhvga (cr=346755 pr=346671 pw=0 time=1046 us)
300 HASH GROUP BY (cr=346755 pr=346671 pw=0 time=299 us cost=126413 size=4950 card=150)
21541174 HASH JOIN (cr=346755 pr=346671 pw=0 time=84263640 us cost=125703 size=680920944 card=20633968)
2153881 INDEX FAST FULL SCAN ORDERS_I2 (cr=11664 pr=11635 pw=0 time=566756 us cost=2743 size=22694870 card=2063170)(object id 86234)
21541174 INDEX FAST FULL SCAN IORDL (cr=335091 pr=335036 pw=0 time=62691616 us cost=87415 size=453947296 card=20633968)(object id 86202)
从 10046 跟踪事件中收集到的统计信息的第一部分与非结果缓存操作相同。如图 3 所示,从 I/O 子系统读取了大约 340,000 行(第 1 步),并且遍历了缓冲区缓存(第 2 步)来获得 300 个汇总行的结果,这些行随后将加载到共享池的结果缓存部分中(第 3 步)。

图 3 Oracle RAC 环境中的结果缓存行为
第 3 步
我们来检查一下结果缓存部分,看看能发现什么。
SQL>SET SERVEROUTPUT ON; SQL>execute DBMS_RESULT_CACHE.MEMORY_REPORT(TRUE); R e s u l t C a c h e M e m o r y R e p o r t [Parameters] Block Size = 1K bytes Maximum Cache Size = 251680K bytes (251680 blocks) Maximum Result Size = 12584K bytes (12584 blocks) [Memory] Total Memory = 207000 bytes [0.004% of the Shared Pool] ... Fixed Memory = 12784 bytes [0.000% of the Shared Pool] ....... Memory Mgr = 200 bytes ....... Bloom Fltr = 2K bytes ....... = 2088 bytes ....... Cache Mgr = 5552 bytes ....... State Objs = 2896 bytes ... Dynamic Memory = 194216 bytes [0.004% of the Shared Pool] ....... Overhead = 161448 bytes ........... Hash Table = 64K bytes (4K buckets) ........... Chunk Ptrs = 62920 bytes (7865 slots) ........... Chunk Maps = 31460 bytes ........... Miscellaneous = 1532 bytes ....... Cache Memory = 32K bytes (32 blocks) ........... Unused Memory = 23 blocks ........... Used Memory = 9 blocks ............... Dependencies = 2 blocks (2 count) ............... Results = 7 blocks ................... SQL = 7 blocks (1 count)
一次查询执行改变了内存报告。0.004% 的动态内存部分分配给结果缓存部分用于存储 9 块数据。这 9 块数据背后的数学原理是怎样的?两个块分配给作为查询一部分的相关对象(ORDER_LINE 和 ORDERS),7 个块分配给查询的结果集。结果集中有 5 列的数据类型均为 NUMBER,结果中包含 300 行。这大约相当于 5.5 个数据块,约等于 6 个数据块,另有一个块用于查询元数据。
注意:结果缓存块大小不应与数据块大小混淆。如报告所示,结果缓存块大小为 1K,数据库块大小为 8K。
结果缓存中存储的一种可检查对象的有用视图就是 V$RESULT_CACHE_OBJECTS 视图。它提供了结果缓存中包含的大多数信息,包括底层数据或对象更改时的对象相关性和失效。
以下数据子集来自 V$RESULT_CACHE_OBJECTS 视图,提供了与查询及其结果相关的基本信息。输出列出了两个相关项和一个结果。结果还包含其他信息,如结果集中的列数和总行数。
SQL> SELECT inst_id INT, ID, TYPE, CREATION_TIMESTAMP, BLOCK_COUNT, COLUMN_COUNT, PIN_COUNT, ROW_COUNT FROM GV$RESULT_CACHE_OBJECTS WHERE INST_ID=&&instNum;
INT ID TYPE CREATION_ BLOCK_COUNT COLUMN_COUNT PIN_COUNT ROW_COUNT
----------------- -------------------------------- ---------- ---------
1 1 Dependency 09-FEB-10 1 0 0 0
1 0 Dependency 09-FEB-10 1 0 0 0
1 2 Result 09-FEB-10 7 5 0 300
以下输出列出了查询(相关项)所使用的全部对象以及存储为结果的查询本身。如本练习的第 1 步所述,结果缓存的区域由一个名为 CACHE_ID 的 ID 标识。只要查询完全相同,Oracle Database 11g 每次将生成相同的 CACHE_ID,无论查询执行了多少次,跨集群中的多少个实例执行。
SQL> SELECT INST_ID INT, ID, TYPE, STATUS, NAME, OBJECT_NO OBJNO,CACHE_ID,INVALIDATIONS INVALS
FROM GV$RESULT_CACHE_OBJECTS WHERE INST_ID=&&instNum;
INT ID TYPE STATUS NAME OBJNO CACHE_ID INVALS
---- --- ---------- -------- -------------------------------- ------ -------------------------- ------
1 1 Dependency Published TPCC.ORDERS 86209 TPCC.ORDERS 0
1 0 Dependency Published TPCC.ORDER_LINE 86201 TPCC.ORDER_LINE 0
1 2 Result Published SELECT /*+ RESULT_CACHE */ OL_W_ID 0 8fbjhchhd9zwh7uhn4mv7dhvga 0
,OL_D_ID,OL_NUMBER,sum(OL_AMOUNT),
sum(OL_QUANTITY) FROM ORDER_LINE O
L, ORDERS ORD WHERE OL.OL_ 注意:在 OBJECT_NO (OBJNO) 列中找到的值对应于 DBA_OBJECTS 视图中 OBJECT_ID 列的值。
继续观察在 V$RESULT_CACHE_OBJECTS 视图中找到的数据子集,以下输出列出了此操作的结果部分的当前空间占用率。
SQL> SELECT INST_ID INT,ID,TYPE,BLOCK_COUNT BLKCNT,COLUMN_COUNT CLMCNT,SCAN_COUNT,ROW_COUNT RCNT,ROW_SIZE_MAX RSM,
ROW_SIZE_AVG RSA,SPACE_OVERHEAD SOH,SPACE_UNUSED SUN FROM GV$RESULT_CACHE_OBJECTS WHERE INST_ID=&&instNum;
INT ID TYPE BLKCNT CLMCNT RCNT RSM RSA SOH SUN
------- --------- ------- ------- ----- ----- ----- ----- -----
1 1 Dependency 1 0 0 0 0 0 0
1 0 Dependency 1 0 0 0 0 0 0
1 2 Result 7 5 300 27 26 536 35
第 4 步
在本练习的第 2 步所讨论的输出中,我们注意到,此操作利用了共享池动态内存部分的 0.004%。以下查询提供了详细报告:
INST_ID POOL NAME BYTES
------- ---------------------------------------------- ----------
1 shared pool Result Cache: State Objs 2896
1 shared pool Result Cache 194216
1 shared pool Result Cache: Memory Mgr 200
1 shared pool Result Cache: 2088
1 shared pool Result Cache: Cache Mgr 5552
1 shared pool Result Cache: Bloom Fltr 2048
现在,我们了解了实例 1 (SSKY1) 上的结果缓存发生了什么。据观察,此功能的行为与单实例实现中相同。
Oracle RAC 最大的优势之一就是多个用户可从集群中的多个实例查询相同的一组数据。如前所述,如果一个用户从实例 2 (SSKY2)、实例 3 (SSKY3) 或实例 4 (SSKY4) 执行完全相同的查询,根据所检索的数据量,将使用缓存融合通过互连传输数据,或者在 Oracle Database 11g 第 2 版中,将从存储本地检索数据(使用全新的 bypass reader 算法)。同样,进程不仅必须通过互连传输所有数据或从存储载入数据,还需要遍历缓冲区缓存内的所有行,之后才能生成结果集。
纵览这次练习,我们在实例 3 (SSKY3) 上执行查询,观察具体情况。如图 3 所示,在 Oracle RAC 环境中使用结果缓存特性的最大性能收益就是仅在实例之间传输最终结果集,从而大大减少了 CPU 和网络资源占用。
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.02 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 21 0.00 0.00 0 0 0 300
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 23 0.02 0.03 0 0 0 300
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 89 (TPCC)
Rows Row Source Operation
------- ---------------------------------------------------
300 RESULT CACHE 8fbjhchhd9zwh7uhn4mv7dhvga (cr=5 pr=0 pw=0 time=299 us)
0 HASH GROUP BY (cr=0 pr=0 pw=0 time=0 us cost=126413 size=4950 card=150)
0 HASH JOIN (cr=0 pr=0 pw=0 time=0 us cost=125703 size=680920944 card=20633968)
0 INDEX FAST FULL SCAN ORDERS_I2 (cr=0 pr=0 pw=0 time=0 us cost=2743 size=22694870 card=2063170)(object id 86234)
0 INDEX FAST FULL SCAN IORDL (cr=0 pr=0 pw=0 time=0 us cost=87415 size=453947296 card=20633968)(object id 86202)
根据从 10046 跟踪事件收集的统计信息,应该注意到,没有本地检索的数据,也不存在对本地实例缓冲区缓存中任意行的遍历。执行计划表明,仅获取最终结果集。这样的结果集是如何获得的?又是从何处获得的?利用缓存融合算法,Oracle 数据库能够过从实例 1 的共享池的结果缓存部分中检索结果集,并通过互连传输数据。这是不是非常巧妙?这种方法切实减少了数据处理,提高了资源利用率。
共享池结果缓存部分的内存结构也与初次执行查询的实例中的内存结构完全相同。这证明了两点:第二个实例无需使用额外的内存或资源,每个 Oracle RAC 实例都在其本地结果缓存内维护自己的一份结果集副本。
R e s u l t C a c h e M e m o r y R e p o r t [Parameters] Block Size = 1K bytes Maximum Cache Size = 251680K bytes (251680 blocks) Maximum Result Size = 12584K bytes (12584 blocks) [Memory] Total Memory = 208144 bytes [0.004% of the Shared Pool] ... Fixed Memory = 13928 bytes [0.000% of the Shared Pool] ....... Memory Mgr = 200 bytes ....... Bloom Fltr = 2K bytes ....... = 3232 bytes ....... Cache Mgr = 5552 bytes ....... State Objs = 2896 bytes ... Dynamic Memory = 194216 bytes [0.004% of the Shared Pool] ....... Overhead = 161448 bytes ........... Hash Table = 64K bytes (4K buckets) ........... Chunk Ptrs = 62920 bytes (7865 slots) ........... Chunk Maps = 31460 bytes ........... Miscellaneous = 1532 bytes ....... Cache Memory = 32K bytes (32 blocks) ........... Unused Memory = 23 blocks ........... Used Memory = 9 blocks ............... Dependencies = 2 blocks (2 count) ............... Results = 7 blocks ................... SQL = 7 blocks (1 count) PL/SQL procedure successfully completed.
在实例 3 上执行的一次查询提供了与实例 1 上的查询完全相同的内存结构;0.004% 的动态内存部分分配给了结果缓存部分,用于存储 9 个数据块。
查询 GV$RESULT_CACHE_OBJECTS 视图,有两个结果缓存部分,一个位于实例 1 上,另一个位于实例 2 上,表明在 Oracle RAC 环境中,Oracle 数据库不维护全局结果缓存部分。而是在实例内本地管理结果缓存。
SQL> SELECT INST_ID INT, ID, TYPE, STATUS, NAME, OBJECT_NO OBJNO,CACHE_ID,INVALIDATIONS INVALS FROM GV$RESULT_CACHE_OBJECTS;
INT ID TYPE STATUS NAME OBJNO CACHE_ID INVALS
--- -- ---------- --------- ---------------------------------- ------ --------------------------- -----
3 1 Dependency Published TPCC.ORDERS 86209 TPCC.ORDERS 0
3 0 Dependency Published TPCC.ORDER_LINE 86201 TPCC.ORDER_LINE 0
3 2 Result Published SELECT /*+ RESULT_CACHE */ OL_W_ID 0 8fbjhchhd9zwh7uhn4mv7dhvga 0
,OL_D_ID,OL_NUMBER,sum(OL_AMOUNT),
sum(OL_QUANTITY) FROM ORDER_LINE O
L, ORDERS ORD WHERE OL.OL_
1 1 Dependency Published TPCC.ORDERS 86209 TPCC.ORDERS 0
1 0 Dependency Published TPCC.ORDER_LINE 86201 TPCC.ORDER_LINE 0
1 2 Result Published SELECT /*+ RESULT_CACHE */ OL_W_ID 0 8fbjhchhd9zwh7uhn4mv7dhvga 0
,OL_D_ID,OL_NUMBER,sum(OL_AMOUNT),
sum(OL_QUANTITY) FROM ORDER_LINE O
L, ORDERS ORD WHERE OL.OL_
6 rows selected.
在上述输出中,有几个因素值得注意:
查询在两个实例上具有相同的 CACHE_ID。
从执行计划(第 4 步)中,我们观察到,实例 3 的结果缓存中的行数与实例 1 相同。
集群有 4 个实例,然而,只有在针对实例的结果缓存已经被利用的情况下,视图才会包含数据,也就是说,实例 2 和实例 4 的结果缓存部分中均无任何条目。然而,当用户在任何一个实例上执行相同的查询时,在实例 3 上观察到的行为将复制到这些实例上。
查询是否会始终返回相同的结果集?当基本对象中的数据发生更改时,将出现怎样的情况?包含结果的结果缓存部分会发生怎样的情况?
这些都是非常好的问题。让我们继续探讨练习,尝试解答部分问题。
当任何基本对象中的数据数据发生更改时,Oracle 数据库将使集群中所有实例上的结果集失效,也就是说,之后再执行相同查询将需要刷新数据处理,以便重新生成结果集,并将结果存储在共享池的结果缓存部分中。
INT ID TYPE STATUS NAME OBJNO CACHE_ID INVALS
--- -- ---------- --------- ---------------------------------- ------ -------------------------- ------
1 1 Dependency Published TPCC.ORDERS 86209 TPCC.ORDERS 0
1 0 Dependency Published TPCC.ORDER_LINE 86201 TPCC.ORDER_LINE 1
1 2 Result Invalid SELECT /*+ RESULT_CACHE */ OL_W_ID 0 8fbjhchhd9zwh7uhn4mv7dhvga 0
,OL_D_ID,OL_NUMBER,sum(OL_AMOUNT),
sum(OL_QUANTITY) FROM ORDER_LINE O
L, ORDERS ORD WHERE OL.OL_
3 1 Dependency Published TPCC.ORDERS 86209 TPCC.ORDERS 0
3 0 Dependency Published TPCC.ORDER_LINE 86201 TPCC.ORDER_LINE 1
3 2 Result Invalid SELECT /*+ RESULT_CACHE */ OL_W_ID 0 8fbjhchhd9zwh7uhn4mv7dhvga 0
,OL_D_ID,OL_NUMBER,sum(OL_AMOUNT),
sum(OL_QUANTITY) FROM ORDER_LINE O
L, ORDERS ORD WHERE OL.OL_
如果再次执行查询从数据库中检索新结果集,则将在结果缓存部分中添加一个新行 (ID=9)。Oracle 数据库保留无效的结果集,直至实例回弹,结果缓存将被刷新,结果缓存中的数据将过期并从缓冲区中删除。
INT ID TYPE STATUS NAME OBJNO CACHE_ID INVALS
--- -- ---------- --------- ---------------------------------- ------ ------------------------- ------
1 1 Dependency Published TPCC.ORDERS 86209 TPCC.ORDERS 0
1 0 Dependency Published TPCC.ORDER_LINE 86201 TPCC.ORDER_LINE 1
1 9 Result Published SELECT /*+ RESULT_CACHE */ OL_W_ID 0 8fbjhchhd9zwh7uhn4mv7dhvga 0
,OL_D_ID,OL_NUMBER,sum(OL_AMOUNT),
sum(OL_QUANTITY) FROM ORDER_LINE O
L, ORDERS ORD WHERE OL.OL_
1 2 Result Invalid SELECT /*+ RESULT_CACHE */ OL_W_ID 0 8fbjhchhd9zwh7uhn4mv7dhvga 0
,OL_D_ID,OL_NUMBER,sum(OL_AMOUNT),
sum(OL_QUANTITY) FROM ORDER_LINE O
L, ORDERS ORD WHERE OL.OL_
3 1 Dependency Published TPCC.ORDERS 86209 TPCC.ORDERS 0
3 0 Dependency Published TPCC.ORDER_LINE 86201 TPCC.ORDER_LINE 1
3 2 Result Invalid SELECT /*+ RESULT_CACHE */ OL_W_ID 0 8fbjhchhd9zwh7uhn4mv7dhvga 0
,OL_D_ID,OL_NUMBER,sum(OL_AMOUNT),
sum(OL_QUANTITY) FROM ORDER_LINE O
L, ORDERS ORD WHERE OL.OL_
在上面的输出中,如果从 ORDER_LINE 表中删除几行,结果行将标记为 Invalid,GV$RESULT_CACHE_OBJECTS 视图的 INVALIDATIONS (INVALS) 计数器将增加。如果稍后对 ORDER_LINE 表执行了另一项操作,无论是添加新行还是再删除几行,计数器都将再次增加。这表明结果缓存中的对象失效了多少次。
它保持无效的时间有多长?与 Oracle 数据库实例中的其他缓存区域一样,结果缓存部分的内存管理也是相同的;无效结果将移动到脏数据列表中,并在新数据载入缓存部分时从缓存部分中刷新出去。
结果缓存的主要局限性在于结果仅存储在缓存中。这意味着,如果实例出错或因维护而关闭,数据将被清除。如果结果集必须永久存储在数据库中,则应使用物化视图等选项。
使用这种特性时,某些类型的操作不受支持。例如,查询不能包括或使用 CURRENT_DATE、CURRENT_TIMESTAMP、LOCAL_TIMESTAMP、SYS_CONTEXT、SYS_GUID、SYS_TIMESTEMP、USERENV 等 SQL 函数。如果使用了这些函数,将发生以下类型的错误:
ERROR at line 1:
ORA-00904: "SYS_TIMESTAMP": invalid identifier
在 SYS 或 SYSTEM 模式中,来自对象/表的结果或数据集不能使用此特性进行缓存。来自数据库序列的 CURRVAL 和 NEXTVAL 伪列的数据不能使用此特性进行缓存。
Oracle Database 11g 第 1 版中引入的结果缓存特性为结果集不常更改的操作提供了显著的收益。该特性可帮助在内存中存储最终结果集,提供对数据集的高速访问。
当数据库中的数据不会因频繁变更而导致数据结果集失效时,这项新特性是非常有帮助的,例如在数据库仓库或报告数据库环境中。
Murali Vallath 拥有 20 余年的数据库设计和开发经验。他在 Summersky Enterprises (www.summersky.biz) 提供独立 Oracle 咨询服务,主要关注 Oracle 数据库的设计和性能调优。Vallath 已为知名企业成功完成了 100 多个大(TB 级)、中、小型 Oracle RAC 实施。Vallath 是一名 Oracle ACE 总监,也是 Oracle 印度用户群 (www.aioug.org) 的创始人。他的博客地址是 http://mvallath.wordpress.com。