Oracle Shared Pool Memory Management

Oracle在管理共享池内存方面面临着难以置信的挑战。多年来的所有改变、bug、补丁和各种性能问题都说明了这一点。虽然这可能会引起一些同情,但当面对与内存管理相关的棘手问题时,同情很快就会转化为愤怒。在本节中,我将解释如何管理共享池内存、多年来的管理进展、如何分配和释放内存、如何处理可能出现的4031错误,以及最后如何解决共享池锁存器争用。

From Hashing to Subpools
在Oracle 7和Oracle 8i中,共享池管理是在一种有趣的哈希结构帮助下执行的。如果还记得我们关于cache buffer 哈希链与library cache哈希链,那么这将非常有意义,但这里存在一种苦恼。当一个进程需要共享池中的内存时,它产生的哈希和链与所请求的内存大小相关。链也通常被称作heap,它是可用内存块链表。因此,从概念上讲,前几个链与大约1KB的内存块相关,后几个链与大约2KB的内存块相关,以此类推。虽然这确实很巧妙,但是经过一段时间对大小不一致的内存进行分配和释放之后,链实际上可以变成几千个节点长。请记住,哈希缓冲区链的大小平均在0到1之间。所以一个由几千个节点组成的链是巨大的。更糟的是,只有一个共享池latch锁来覆盖所有哈希链!清洗共享池帮助很大,因为链将减少到一个可观的规模。但这无法操作大型生产数据库,因此Oracle不得不进行更改。

Oracle9i引入了子池,这自然会导致多个共享池锁存器。基于哈希的策略被多个子池替换,每个子池包含一个在标准LRU策略上操作的堆。Oracle也开始标准化内存需求大小,这增加了找到可接受内存大小块的可能性。子池、多个共享池锁latch和LRU策略极大地减少了共享池内存管理问题。如果您同时管理过Oracle8i和Oracle9i系统,您可能会经历这种变化,并注意到有很大的不同。

数据库系统中共享池子池的数量可以通过查看实例参数_kghdsidx_count或通过计算x$kghlu视图中的行数来判断。

下面的查询显示了与共享池子池相关的一系列SQL语句。在这个例子中,一个大小为800MB的共享池存在三个子池。x$ksmss查询对于每个子池返回一行记录并且如果存在java pool还会另外加一行记录。设置子池数量的实例参数_kghdsidx_count不能被动态修改。如果你想影响Oracle调用一个子池号发生改变,你必须设置实例参数并回收实例。

SQL> @spspinfo
SQL> select sum(bytes/1024/1024) sp_size
2 from v$sgastat
3 where pool='shared pool';
SP Size (MB)
------------
         800
SQL> select count(*) no_sp from x$kghlu;
Num of SPs
----------
         4
SQL> select INST_ID, KSMDSIDX, KSMSSLEN
2 from x$ksmss
3 where ksmssnam='free memory';
INST_ID    KSMDSIDX   KSMSSLEN
---------- ---------- -----------
1          0          301989888
1          1          18818468
1          2          12659340
1          3          7697300
1          4          20482152
SQL> select i.ksppinm param,v.ksppstvl value
2 from x$ksppi i, x$ksppcv v
3 where v.indx=i.indx
4 and v.inst_id=i.inst_id
5 and i.ksppinm='_kghdsidx_count';
PARAM                VALUE
-------------------- -----
_kghdsidx_count      4

Oracle对子池的数量设置了严格的限制。在Oracle 11g中,可以使用7个共享池子池来启动实例,但有8个子池,该实例没有启动——实际上,在重新启动之前需要关闭实例。

有趣的是,Oracle不必遵从子池号的意愿。实际上,在一个类似于上面查询结果的Oracle数据库11.1g的示例中,实例参数被设置为2,实例重新启动,但是Oracle创建了三个子池。在Oracle数据库11.2g中,实例参数再次被设置为2,实例重新启动,并且按照指定的Oracle创建了两个子池。在没有手动设置实例参数的情况下运行Oracle数据库11.1g和11.2g, Oracle只创建了一个子池。因此,尽管你可以影响甲骨文,它仍然保留做出改变的权利。

内存分配与回收
内存分配是相当简单的。它遵循标准的LRU算法并与pinning与locking一起使用。当一个Oracle进程(服务器或后台进程)请求内存时,Oracle内核中的一部分称作为heap manager(堆管理器)的会被执行。虽然细节不断变化,但概念算法基本相同。

Oracle进程需要特定数量的内存,这些内存被转换为多个特定大小内存块的请求。堆管理器搜索与每个请求匹配的单个大小的内存块。多个内存块(认为是非连续的)是不行的。如果进程请求4KB内存,堆管理器必须从共享池内存中返回4KB内存块的地址。

在Oracle9i中,Oracle进程获得子池latch,并将在放弃之前搜索子池至多5次。因为内存的情况可能会发生急剧且快速的变化,允许多次传递会增加找到内存的可能性。然而在五次搜索之后,当持有各自共享池latch时,如果合适的大小的内存块没有找到,Oracle将会放弃,并posts错误代码4031,“out of mmemory”信息,并且会话将会停止处理。对于每个Oracle DBA来说,这在生产系统中将是不可接受的。

在Oracle Database 10g中,Oracle进程对内存的要求更加强烈。如果在五次搜索之后在当前共享池中没有找到合适的内存,进程将移动到另一个子池。这一过程将继续进行到所有定义的子池被搜索完为止。如果在这时,没有找到合适的内存,就像以前一样,Oracle将会放弃并posts 4031错误并且停止处理。Oracle在这个版本中所做的是消耗更多CPU和更长时间地持有共享池latch来减少返回错误消息的机会。从数据库操作的角度来看,性能较慢总比没有性能好。在我们解决性能问题时,至少可以执行工作。

当内存不足时,Oracle将回收不被频繁访问的内存块。可能在尝试检索SQL语句的文本时遇到过这种情况,并且它不再缓存在共享池中。幸运地是,Oracle不会回收内存供其它对象使用。例如,如果一个游标被pinned(固定)了,Oracle将不会回收相关的内存,不管该内存是不是被频繁访问。事实上,即使清除共享池也不会删除被固定的游标。如果真的想清空共享池并且想从头开始,可以重启实例。

共享池latch竞争识别与解决
共享池latch被用来序列化共享池内存的管理。这意味着像搜索内存,LRU活动,分配内存与回收内存请求共享池latch这样的操作。因为从Oracle 9i开始存在多个子池,并且每个子池有它自己的共享池latch,只要使用这个版本或之后的版本就可以大大减少共享池lat这个特别的解决方案非常简洁,因为它只需要很少的工作,而且我们不是在玩弄Oracle的共享池LRU算法。但是,请记住,更多的子池可能需要更多的共享池内存,需要重新启动实例才能使实例参数更改生效,Oracle保留不尊重您的建议的权利ch竞争的可能性。但有些时候这些仍然不够。下面有些方案可能减少latch获取时间latch持有时间或者两者都减少。

固定大且频繁使用的对象
此策略用来确保对象成功进入缓存,不用管内存活动或对象大小。任何包第一次被调用时,整个包被加载到内存中。操作中在激活共享池后如果需要触发,将强制执行大量的内存管理活动,这将导致对象不能被加载而触发4031错误。即使如果对象被成功加载,用户可能会注意到应用程序的延迟。

有些时候可能想要固定小对象。例如,假设一个对象有一种高强度活动模式,长时间的暂停导致对象的内存被释放,然后是另一段高强度活动。为了确保没有应用程序延迟且为了减少内存管理,我们可以简单固定对象。

大多数大型Oracle应用程序都提供一个脚本,其中包含要固定在共享池中的对象,并且它们将建议在实例启动后立即运行该脚本。重要的是要知道,即使您的应用程序供应商提供了这样一个列表,您也可以通过了解您的组织实际使用对象的方式来细化这个列表。供应商应用程序开发人员通常会创建固定列表。然而大多数应用程序开发商认为他们的对象是最重要的并且应该总是被固定。但实际上,很多时候,在应用程序在生产环境中运行之前,没有人真正知道您的组织将如何使用它。因此如果出现4031错误,这对于修改固定列表来说是一个好消息。

想要确保对象总是固定在共享池中有四个简单步骤要操作。关键词pin常被使用,dbms_shared_pool包的keep函数被用来确保对象保留在共享池中。缺省情况下当创建数据库时这个包不会被加载,因此第一步就是要加载它。下面的代码就是用来创建这个过程。

[oracle@jytest1 ~]$ sqlplus / as sysdba

SQL*Plus: Release 12.2.0.1.0 Production on Mon May 6 14:30:28 2019

Copyright (c) 1982, 2016, Oracle.  All rights reserved.


Connected to:
Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
SQL> @$ORACLE_HOME/rdbms/admin/dbmspool.sql

Session altered.


Package created.


Grant succeeded.


Session altered.

下一步骤是找到大的或频繁的对象。Oracle保持对共享池对象使用进行跟踪并且可以通过v$db_object_cache视图来查看这些信息。下面是使用OSM脚本dboc.sql来识别潜在的对象。您可能会看到一组比其他包大得多的包,以及执行得比其他包频繁得多的包。还可能有一些对象,您个人知道它们具有不同寻常的执行配置文件,而您希望缓存它们。

一旦有了要保存的对象列表,下一步就是确定如何将它们放入缓存中。keep函数用于固定对象,或者更好地说,用于将对象保存在共享池中。

SQL> @dboc 10 20
old   9: where  a.sharable_mem >= &min_size
new   9: where  a.sharable_mem >= 20
old  10:   and  a.executions >= &min_exec
new  10:   and  a.executions >= 10

DB/Inst: jy/jy2                                                   07-May 08:26am
Report:   dboc.sql             OSM by OraPub, Inc.                Page         1
                      Oracle Database Object Cache Summary

                                                 Obj          Exe  Size
Owner        Obj Name                            Type Loads   (k)  (KB) Kept?
------------ ----------------------------------- ---- ----- ----- ----- -----
SYS          DBMS_STATS_INTERNAL                 PBDY     0    32   492 NO
SYS          PLITBLM                             PKG      0     8     8 NO
SYS          DBMS_ASSERT                         PBDY     0     6    16 NO
SYS          STANDARD                            PBDY     0     3    32 NO
SYS          DBMS_STATS_INTERNAL                 PKG      0     1   122 NO
SYS          DBMS_SQLDIAG                        PBDY     0     1    40 NO
SYS          DBMS_SQLTUNE_UTIL0                  PBDY     0     1    16 NO
SYS          DBMS_AUTO_TASK                      PBDY     0     0    24 NO
SYS          DBMS_AUTO_TASK                      PKG      0     0    28 NO
SYS          DBMS_STANDARD                       PKG      0     0    44 NO
SYS          DBMS_ADVISOR                        PBDY     0     0    69 NO
SYS          DBMS_SQLTUNE_UTIL2                  PBDY     0     0    20 NO
SYS          DBMS_UTILITY                        PKG      0     0    12 NO
SYS          PRVT_ADVISOR                        PBDY     0     0   176 NO
SYS          DBMS_SQLTUNE_UTIL1                  PBDY     0     0    57 NO
SYS          DBMS_STATS_ADVISOR                  PBDY     0     0   167 NO
SYS          DBMS_SYS_ERROR                      PBDY     0     0     8 NO
SYS          DBMS_OUTPUT                         PBDY     0     0    12 NO
SYS          DBMS_UTILITY                        PBDY     0     0    57 NO
SYS          DBMS_PDB                            PBDY     0     0    12 NO
SYS          DBMS_STATS_ADVISOR                  PKG      0     0    24 NO
SYS          DBMS_SQLTUNE_INTERNAL               PBDY     0     0   532 NO

22 rows selected.


SQL> l
  1  select a.owner ownerx,
  2         a.name  namex,
  3         decode(a.type,'PACKAGE','PKG','PACKAGE BODY','PBDY','FUNCTION','FNC','PROCEDURE','PRC') typex,
  4         a.loads/1000 loadsx,
  5         a.executions/1000 execsx,
  6         a.sharable_mem/1024 sizex,
  7         a.kept keptx
  8  from   v$db_object_cache a
  9  where  a.sharable_mem >= &min_size
 10    and  a.executions >= &min_exec
 11    and  a.type in ('PACKAGE','PACKAGE BODY','FUNCTION','PROCEDURE')
 12* order by executions desc, sharable_mem desc, name

为了将一个游标保存在共享池中,从v$sql,v$sqlarea或者v$open_cursor中收集它的地址与哈希值。下面的代码显示地址(6877c238)和哈希值(1356456286)在它们之间使用逗号进行连接作为一个参数输入,第二个参数是C,因为我们要保存一个游标。对于保存触发器参数为T,对于序列,使用Q,对于包,过程与函数,参数为P。

SQL> exec dbms_shared_pool.keep('6877C238,1356456286','C');
PL/SQL procedure successfully completed.

上面的代码片段可以用于编程结构,但是大多数人发现下面的选项最容易使用。下面的代码用来保存jy方案中的TuoMi过程。

SQL> exec dbms_shared_pool.keep('jy.TuoMi');

PL/SQL procedure successfully completed.

最后,在发出上述代码片段之后,您可以轻松地进行检查,以确保确实保存了对象。从下面的输出结果可以看到jy.TUOMI过程对象的Kept列被设置为YES。

SQL> @dboc 0 0
old   9: where  a.sharable_mem >= &min_size
new   9: where  a.sharable_mem >= 0
old  10:   and  a.executions >= &min_exec
new  10:   and  a.executions >= 0

DB/Inst: jy/jy2                                                   07-May 08:48am
Report:   dboc.sql             OSM by OraPub, Inc.                Page         1
                      Oracle Database Object Cache Summary

                                                 Obj          Exe  Size
Owner        Obj Name                            Type Loads   (k)  (KB) Kept?
------------ ----------------------------------- ---- ----- ----- ----- -----
SYS          DBMS_STATS_INTERNAL                 PBDY     0    32   492 NO
SYS          PLITBLM                             PKG      0     8     8 NO
SYS          DBMS_ASSERT                         PBDY     0     6    16 NO
..............
SYS          DBMS_SQLTUNE_INTERNAL               PKG      0     0    71 NO
JY           TUOMI                               PRC      0     0    36 YES
SYS          DBMS_SMB_INTERNAL                   PBDY     0     0    32 NO
SYS          DBMS_SQLTUNE                        PKG      0     0    32 NO
..............
99 rows selected.

经常有人问我,多长时间修改一次固定列表。就我个人而言,除非有很好的理由,否则我不喜欢调用任何数据库更改。改进固定列表的一个很好的理由是,如果系统突然开始出现共享池latch争用,或者遇到4031个错误。这一点非常重要:如果添加了应用程序功能、发生了应用程序升级或应用程序使用发生了显著变化,则从更主动的角度细化固定列表。

清空共享池
虽然不在任何列表的最上面,但是只要刷新共享池就可以立即缓解共享池latch争用。对于oracle9i之前的系统尤其如此,那时还不存在子池。这很明显不是一个最优解决方案,因为每个对象都没有固定在共享池中将被删除并且它们的内存会被回收。初始结果可能会适得其反,因为它可能会导致立即进行大量的硬解析,正如我们所知,这会消耗大量CPU资源,并强制执行非自然数量的锁。然而,这种不幸的情况很快就会平息下来。

有时,共享池大小的组合,数据库版本(Oracle 9i)与应用程序的使用将使DBA别无选择,只能计划定期共享池刷新。这就是现实情况。

如下面的代码片段所示,刷新共享池非常简单,但效果确实显著

SQL> alter system flush shared_pool;

System altered.

增加子池数量
最简单、最强大和最合适的共享池latch解决方案之一是简单地添加子池,增加子池也将增加共享池latch。前面的“从哈希到子池”小节详细介绍了这个过程。这个特别的解决方案非常简洁,因为它只需要很少的工作,而且我们不是在玩弄Oracle的共享池LRU算法。但是,请记住,更多的子池可能需要更多的共享池内存,需要重新启动实例才能使实例参数更改生效,Oracle保留不尊重您的建议的权利。

减少共享池大小
这听起来可能很奇怪,在子池存在之前,增加共享池大小最终可能导致共享池latch争用。每一种算法的性能都是有限的,都是针对特定情况而设计的。当情况发生变化时,算法可能无法按预期执行。不要忘记,增加缓存来支持更多的活动几乎总是需要更多的CPU资源来管理。因此,可能会有一个收益递减点。Oracle最初的共享池内存管理算法在大约600MB的共享池中运行得相当好,但是当它达到750MB左右时,dba开始看到大量的共享池latch争用是非常常见的。一旦引入了子池,特别是与我概述的其他解决方案相结合,共享池latch争用就可以成功地解决。

4031错误解决方案
Oracle在决定什么时候放弃,什么时候继续使用CPU和保持latches之间有一个微妙的平衡。多年来,Oracle耗尽共享池内存的可能性已经降低,但是4031错误的几率仍然高度依赖于Oracle共享池内存的数量和应用程序。下面是一个实际的4031错误消息

ORA-04031: unable to allocate 4192 bytes of shared memory ("shared
pool","SELECT * F...","sql area (6,0)","kafco :
qkacol"):4031:375:2008:ocicon.c

上面的信息显示,4KB内存正尝试在子池6中进行分配,但是由于某些原因,不能完成分配。幸运地是有一些方法来减少收到4031错误的机会。

清空共享池
与解决共享池latch争用一样,4031错误的一个解决方案也是清空共享池。虽然没有DBA愿意承认定期清空共享池,但这仍然有效。根据Oracle版本、分配的共享池内存的数量以及应用程序独特的内存使用模式,这可能是您的最佳选择。对于oracle 9i之前的系统尤其如此。

增加共享池大小
从概念上讲,增加共享池内存为Oracle提供了更大的灵活性来满足内存请求。然而,除了好处之外,在转移计算资源时也总是有成本的。在大多数情况下,收益实际上大于成本,因此,如果操作系统有可用内存,或者可以将内存从其他Oracle缓存转移到共享池,增加共享池内存很可能减少4031个错误。

请记住,每当您要求Oracle管理更多内存时,都需要更多的CPU来管理这些内存。在oracle9i之前的系统中尤其如此,因为可能存在非常长的内存链堆。如果链有数千个块长,而4031个错误可能会消失,那么在试图获取共享池latch和扫描长链时,这种情况可能会表现为严重的共享池latch争用和大量CPU消耗——所以要小心。

如Oracle文档所述,如果您通过自动内存管理获得解放,您可能需要设置最小的共享池大小。在增加缓冲区缓存的过程中,Oracle会自动减少共享池的大小,以至于开始出现4031个错误。

增加共享池保留大小
当一个较大的包最初被加载到一个已经非常活跃的共享池中时,就会出现一个常见的内存分配挑战。共享池越活跃,特别是当它很小并且对象大小非常不同时,就越有可能找不到所需的内存。

假设我们的服务器进程需要内存来存储一个大游标。当Oracle搜索共享池内存时,如果对象大小大于阈值,Oracle首先搜索保留区域。如果在保留区域中没有找到内存,Oracle将到非保留区域搜索。这种策略有助于将较小的对象排除在保留区域之外,从而将其保留给较大的对象。

有三个实例参数可以组合使用:
.shared_pool_reserved_size被用来直接设置共享池保留大小以字节为单位
.隐藏参数_shared_pool_reserved_pct,它的缺省值为5(5%),可以被用来代替shared_pool_reserved_size。
.一个相对大的对象是由实例参数_shared_pool_reserved_min_alloc来定义的,它的缺省值为4400字节。有趣的是缺省值4400字节仅仅比常见的单个块请求4096字节大。因此,在默认情况下,Oracle表示任何大于一个典型大小的内存块请求都被认为是大的,因此应该从保留的大小中获得内存。

前两个参数中的任何一个都可以用来为相对较大的对象设置共享池保留内存大小。如果您设置其中一个参数,Oracle将计算另一个参数。通过仔细调整这些参数,性能分析人员可以增加进程找到大量内存的可能性,同时仍然维护大量内存给相对较小对象使用。虽然这些参数通常不会调整,但如果发生4031错误,它们的小心调整可能会修复问题。

最小化游标固定时间
当执行游标时,游标也被固定。毕竟,您不希望SQL语句在执行期间突然消失!这是好消息。潜在的坏消息是,当执行完成时,固定游标被释放。如果没有其他进程固定游标,Oracle可以随意销毁,即释放关联的内存。现在假设有人想重新执行游标。如果它已被释放,将执行硬解析,因为整个游标将被重建!每个应用程序使用模式都是独特的;因此,当与更小的共享池或许多独特的SQL语句(或者两者都有)结合使用时,内存管理和库缓存活动可能会变得异常紧张。减少硬解析的一种方法是固定游标,使它们不能被释放。

Oracle提供了一个特殊的实例参数,该参数将保持所有会话的所有游标固定到关闭游标为止。但是,这种好处是以增加共享池内存消耗为代价的,因此,增加了接收4031错误的可能性。Oracle非常清楚这一点,所以为了鼓励回收释放内存并降低发生4031个错误的可能性,cursor_space_for_time实例参数默认设置为false。

如果系统正经历4031错误,你应该要检查cursor_space_for_time参数值。如果你的系统在过去某个时间点已经经历了严重的共享池latch急用,那么可以理解有人将cursor_space_for_time设置为true了。虽然你可能不会决定设置cursor_space_for_time参数为false。但这是一个有效选项应该被考虑。

减小保留对象的内存消耗
如果有太多对象通过执行dbms_shared_pool.keep过程被强制保留在共享池中,它们可能会消耗大量的内存Oracle可能无法成功地管理剩下的内存。此外,如果没有将大型对象保存在共享池中,则实例已经运行了一段时间,然后引用该对象,当强制加载该对象时,内存可能不可用。关键是不要随意地将对象保存在共享池中。

升级数据库版本到10gr2
当然,4031个错误不是升级的惟一原因,而是从Oracle数据库10gr2开始将内存标准化为4KB块。虽然我永远不会仅仅因为这个改进就建议升级到这个版本,但是这可能是升级的一部分原因。

就像对段区大小进行标准化一样,拥有标准的内存块大小可以提高快速找到合适内存的可能性。可以找到的内存越快,消耗的CPU周期就越少,必须持有共享池latch的时间就越短,存在大量浪费的小内存块的可能性就越小(增加4031错误的可能性)。

Oracle Write List and Database Writer

Write lists通常也叫做dirty lists或LRU-W lists,是由整个dirty buffer headers组成。每个dirty bufferheader也存放在CBC结构中。Oracle有一个工作集的概念,它由LRU latch,LRU chain与write list组成。每个数据库写进程与一个或多个工作集关联。在实例启动时,Oracle将决定工作集的数据量与数据库写进程的数量(db_writer_processes,它的缺省值为1),然后设置它们的关联。当一个数据库写进程执行操作时,它将从它的写列表中获取信息。服务器进程与数据库写进程从它们瓣LRU chains中移动不被频繁访问的dirty buffers到与它们相关的写列表中。

数据库写进程的运转
多块写比单块写更有效,因此数据库写进程在执行写操作之前会构建一个dirty列表。多年以来,Oracle已经修改了实例参数与算法来控制最小的dirty列表的批量大小。事实上,Oracle使用了一种自调整的算法来计算dirty buffers的突发事件。当数据库写进程正在进行多块写操作时,它将出现db file parallel write等待事件。v$session_wait视图中的参数用来提供正在被写到磁盘的数据块数。数据库写进程的职责是将dirty buffers写入磁盘。执行下面的查询:

SQL> select count(*) from v$bh where dirty='Y';

  COUNT(*)
----------
       155

SQL> select count(*) from v$bh where dirty='Y';

  COUNT(*)
----------
       185

SQL> select count(*) from v$bh where dirty='Y';

  COUNT(*)
----------
       121
SQL>  select count(*) from v$bh where dirty='Y';

  COUNT(*)
----------
       172

SQL>  select count(*) from v$bh where dirty='Y';

  COUNT(*)
----------
       173

从上面的查询结果可以看到dirty buffer数量是呈循环性的增长与减少。循环的结果是当数据库写进程将dirty buffers写入磁盘时,这些dirty buffers将再一次变为free buffers。这种循环是正常的,也是想要看到的。如果dirty buffers的计数一直在增长,那么你就知道数据库写进程处理能力不足。对于大型Oracle系统来说通常有上千个dirty buffers存在。

我们都知道数据库写进程每3秒会被唤醒一次。通过跟踪来查看在Oracle 11g中是不是也是这样。注意休眠时间是大概3秒钟并且系统调用为semtimeodop。而当一个服务器进程在获取latch期间休眠时,因为它执行的是select系统调用。select不允许这具进程被唤醒,但信号量调用可以。这是很重要的区别,因为数据库写进程由于各种原因需要被唤醒,比如检查点操作或free buffer waits等待事件。

[root@db1 ~]# ps -eaf |grep dbw
oracle    49087      1  0  2018 ?        03:20:23 ora_dbw0_RLZY1
oracle    49089      1  0  2018 ?        03:19:18 ora_dbw1_RLZY1

[root@db1 ~]# strace -rp 49089
......
0.000298 semtimedop(3407933, {{25, -1, 0}}, 1, {3, 0}) = -1 EAGAIN (Resource temporarily unavailable)
......
0.000482 semtimedop(3407933, {{25, -1, 0}}, 1, {3, 0}) = -1 EAGAIN (Resource temporarily unavailable)
......
0.000336 semtimedop(3407933, {{25, -1, 0}}, 1, {3, 0}) = -1 EAGAIN (Resource temporarily unavailable)
......
0.000608 semtimedop(3407933, {{25, -1, 0}}, 1, {3, 0}) = -1 EAGAIN (Resource temporarily unavailable)
......

数据库写进程相关竞争的识别与解决
有各种与数据库写进程相关的等待事件。对争用情况进行分类的一种方法是理解数据库写进程是否有“push-to-disk”问题或“pull-from-write-list”问题。大多数的问题是push问题,也就是写磁盘的问题。但也有一种很常见的pull问题稍后再进行说明。与数据库写进程push-to-disk问题相关的所有等待事件都是以db file开始。与其它IO等待事件一样,在IO调用之前和之后,会执行gettimeofday调用,并且区别就是我们通过Oracle等待事件接口所看到的。这里有两个常见的数据库写进程push-to-disk等待事件:
.db file parallel write
目前最常见的数据库写进程等待事件,一个parallel写也可以简化为多块写。这是数据库写进程从写列表中获取数据并将dirty块批量写入磁盘的结果。希望这个等待事件的等待时间小于5ms,但每个单位有它自己的预算与服务要求。写操作的时间小于5ms说明写缓存工作的很好。

.db file single write
它不应该是top等待事件。当所有数据库文件头块在写入检查点操作的末尾可能会出现。这是通过一次执行多个单块写来完成的。

从需求和能力方面来查看IO问题。当存在一个IO问题时,需求已经超过能务了。只有当锁或阻塞类型出现时,比如free buffer waits事件才会出现例外。当看到数据库文件写操作出现问题时,除了锁或阻塞的原因之外,知道IO请求已经超过了IO子系统的能力。使用复杂IO管理将增加不同系统文件与数据库文件存储在相同磁盘上的机会。

IO问题可能变得非常情绪化。供应商参与进来并开始保护自己的地盘。为了帮助解决问题,从应用程序角度来看,将查找生成dirty buffers的SQL语句。将会找到一个或多个更新,插入与删除操作

从Oracle角度来分析,可以考虑任何可能增加Oracle IO写效率的参数。例如研究可以增加数据库写进程批量写大小的方法。修改数据库写进程的批量大小与数据库版本有关,例如_db_block_write_batch与_db_writer_max_writes参数。也可以考虑增加参数_db_writer_max_scan_pct(缺省值是40,例如40%)或_db_writer_max_scan_cnt,在触发数据库写进程开始执行写操作之前它们用来判断一个服务器进程将扫描多少LRU buffer headers。增加这些参数将提供更多时间来构建写列表,因此造成每个数据库写IO请求将写入更多数据块。这将有效的增加每秒写入磁盘的数据块。测试显示通过将db_writer_max_scan_pct从5增加到95,数据库写进程操作系统写调用将减少9%且db file parallel write等待减少3%,当事务活动增加14%时,每秒的块改变将增加19%。仅仅通过改变这个参数,当工作量增加时IO活动减少了。

另一种可能是遇到服务器进程不触发数据库写进程执行写操作,因此会构建写队列。当一个服务器进程,在搜索一个free buffer时,偶然发现一个不被频繁访问的dirty buffer后将其移动到相关的写列表,并且也会检查写列表是否足够长可以执行写操作了。如果写列表已经足够长了,服务器进程将触发数据库写进程执行写操作。因此这是一个有效的选择,为了允许构建写队列来导致大批量写操作,可以增加_db_large_dirty_queue(在某些系统中缺省值是25)参数。但创建太在的写队列需要小心。当dirty buffers正被写入磁盘时,它们不能被改变。任何需要改变正被写入磁盘的buffer都必须等待。相关的等待事件为write complete waits。在top等待事件中write complete waits不是很常见,但如果修改了写列队长度就可能会出现。

最后,从Oracle角度来看,增加buffer cache可以让数据库写进程减轻在短时间内强烈的数据块改变所带来的压力。一个大的buffer cache允许cache来填充dirty buffers,在不强制数据库执行写操作时,有更多的时间来创建大的且更有效的批量写。如果真的想给数据库写进程施加压力,创建一个小的buffer cache并执行一些DML操作,你将会看到数据库写进程疯狂地试图删除脏缓存区中的小缓存。

从操作系统角度来看,这应该存在IO瓶颈。这是很罕见的,如果IO写响应时间小于5ms而写缓存工作的很好,那么总的等待时间将足够大,因此db file parallel write等待事件将被推到top等待事件中。当出现这种情况时,关注IO子系统不会有什么实质性的效果。在这种情况下,关注减小应用程序IO写活动与增加Oracle写效率。这也意味着要在数据库活动高峰期来创造性的减小写操作。经验丰富的DBA已经看到了在正常业务期间存在写密集的IO活动,比如RMAN,血份与文件传输。减少非Oracle IO活动能有效地增加IO子系统的能力。

Oracle Least Recently Used Chains

LRU Chains(or LRU lists)有它们相关的算法在过去已经修改过多次。尽管算法已经修改过,但LRU chain的功能仍然相同:为了帮助被频繁访问的buffer内置在cache中和帮助服务器进程快速地找到可被替换的buffers。任何时候单个列表都要努力地完成这两个任务,这将可能出现一些妥协。LRU chain也不例外,正如你将要发现的一样,Oracle当前的LRU算法实现的非常好,支持buffer caches超过100G的大小来满足电信与政府系统的高事务处理的要求。

在Oracle 6中,只有单个LRU chain被单个LRU chain latch保护着。在大型的OLTP系统中,DBA将与LRU chainlatch竞争进行斗争。但从Oracle 7开始,Oracle通过将单个LRU chain分割成多个小的LRU chains,每个都有一个相关的LRU chain latch来缓解这种问题。每个cache buffer存放在CBC结构中并存放在一个LRU chain或一个写列表(也叫脏列表)中。buffers不会同时存放在一个写列表与一个LRU列表中。LRU chains要比CBCs长太多。

脏buffers存放在一个LRU chain中不是问题。事实上,如果脏buffers不能存放在一个LRU chain上将会影响性能。LRU chains的一目标就是将被频繁访问的buffers保留在cache中,并且许多脏buffers也会被频繁地访问。当在数据库检查点期间,每个脏buffer将被写入磁盘并再次变为free buffer。

隐含参数_db_block_lru_latches显示实例正在使用的的LRU chains的数量。与CBCs一样,每个LRU chain latch控制着一组LRU chains的序列化。
SQL> select x.ksppinm NAME,y.ksppstvl value,x.ksppdesc describ
2 from x$ksppi x, x$ksppcv y
3 where x.inst_id=USERENV(***216;Instance***217;)
4 and y.inst_id=USERENV(***216;Instance***217;)
5 and x.indx=y.indx
6 and x.ksppinm like ***216;%&par%***217;;
Enter value for par: _db_block_lru_latches
old 6: and x.ksppinm like ***216;%&par%***217;
new 6: and x.ksppinm like ***216;%_db_block_lru_latches%***217;

NAME VALUE DESCRIB
***212;***212;***212;***212;***212;***212;***212;- ***212;***212; ***212;***212;***212;***212;***212;***212;***212;***212;***212;***212;***211;
_db_block_lru_latches 640 number of lru latches

LRU Chain随着时间的推移而变化
当前的LRU chain算法被叫做touch-count算法,它使用计算频率方案在每个buffer header上设置一个数字。但是Oracle花了很多年才实现这个算法。理解Oracle的LRU算法的发展更能了解LRU chains是如何工作的,它的缺点是什么以及如何确保它们按需执行。

当LRU chains出现性能问题时,大量的LRU chain latch竞争将会出现。从Oracle算法角度来说,latch问题通常会造成服务器进程在搜索一个free buffer时持有一个lRU chain latch的时间太长。这里存在许多相互关联的原因,其解决方案也是一样。

Standard LRU Algorithm(标准LRU算法)
不管Oracle的LRU算法如何,每个Oracle LRU chain有一个最近最少使用(LRU)端,也有一个最近频繁使用(MRU)端。笼统地说,被频繁访问的buffer header将存放在靠近MRU端,并且不被频繁访问的buffer将存放在靠近LRU端。

标准LRU算法是非常简单的。当一个buffer被放入cache中或被访问时(查询或DML操作),buffer将被存放在会话相关的LRU chain(每个会话与一个LRU chain相关)的MRU端。这种想法是一个被频繁访问的buffer将被重复touched并且会被重复移动到LRU chain中的MRU端。buffer移动到LRU chain的MRU端通常叫做buffer promotion。如果一个buffer不被频繁访问,那么其它的buffer将被promoted或插入到LRU chain中,不被频繁访问的buffer将被移动到LRU chain的LRU端。

靠近每个LRU chain的LRU端可能潜伏着一个服务器进程用来查找一个可用的不被频繁访问的buffer好让刚刚从磁盘中读取来的块替换掉它。假设LRU chain是8个buffer header那么长,全表扫描会扫描8个数据块,并且每个数据块将读入Oracle cache中并且buffer headers会被放入LRU chain中。当标准LRU算法使用时,只有一个LRU chain,因此整个LRU链将被全表扫描所访问的数据块所替换。随着时间的推移包含被频繁访问的buffer已经被替换了。用户肯定会注意到性能的变化,并且IO子系统也将受到打击。当数据库大小继续增长的时候,Oracle显然不得不进行改进,所以修改了LRU算法。

Modified LRU Algorithm(修改后的LRU算法)
Oracle著名的LRU算法修改是在Oracle 6中。它是一次重大成就并且Oracle开发者确实应该对他们的高级buffer cache算法感到自豪。在这之后,它确实解决了标准LRU算法的关键问题。

修改后的LRU算法与标准LRU算法仅有的区别是对LRU chain的LRU端的几个buffer创建了一个窗口(用来存放被频繁访问的buffers)。这个窗口的大小只有几个buffers(例如,4个)并且可以通过隐含参数_small_table_threshold来进行修改。这可以确保不管对多大的表进行全表扫描都将不会对cache产生什么影响。

Oracle修改后的LRU算法对一些buffer headers创建了一个窗口,当所有全表扫描(FTS)的buffer headers被读入到buffer cache时会经过这个窗口。这确保了放在LRU chain中的MRU端的被频繁访问的buffers不会被替换掉。

与其它所有算法一样,修改的LRU算法也有限制,但这么多年来这些限制没有造成问题。然而,一旦客户开始使用Oracle来开发大型数据仓库应用程序时,两个显著的问题会出现:
.大型数据仓库有大量的索引,并且当大量索引使用范围扫描时,成千上万的索引叶子块必须被读入cache中。这个问题直到Oracle 8i,如果索引叶子块不在buffer cache中,Oracle将产生一个单块IO请求(db file sequential read)将数据块放入buffer cache。令人吃惊的是因为这不是一个多块IO请求,索引buffer被插入到LRU chain的MRU端,这破坏了开发良好的cache,现在完全存放着索引叶子块buffers。

.当数据块被请求时(基于索引叶子块),它们也会从IO子系统中(db file sequential read)被请求一次,因此再一次这些数据块被放入到LRU chain中的MRU端。当Oracle系统大小增加时,Oracle的buffer cache减少了使用性。

Oracle***217;s Touch-Count Algorithm
在Oracle 8.1.5中Oracle引入了一种完全修改好的LRU chain算法已经完全消除了所有LRU chain latch竞争问题。关于这种修改没有任何文档记录。发现算法改变是因为看到了新的隐含参数_db_percent_hot_default 和_db_aging_cool_count。当有新的参数出现或有旧的参数丢弃时,算法肯定有被修改。Oracle确实实现了计算机科学领域中通常所说的计数频率方案。

SQL> select x.ksppinm NAME,y.ksppstvl value,x.ksppdesc describ
  2  from x$ksppi x, x$ksppcv y
  3  where x.inst_id=USERENV('Instance')
  4  and y.inst_id=USERENV('Instance')
  5  and x.indx=y.indx
  6  and x.ksppinm in('_db_percent_hot_default','_db_aging_cool_count');

NAME                          VALUE      DESCRIB
----------------------------- ---------- ------------------------------------------------
_db_percent_hot_default       50         Percent of default buffer pool considered hot
_db_aging_cool_count          1          Touch count set when buffer cooled

正如你所期待的,通用方法就是每次触及buffer header时递增计数器。更频繁访问的buffer headers将有更高的触及计数并且确实访问更频繁,因此buffer将被保留在buffer cache。Oracle***217;s touch-count算法判断buffer header是否被频繁访问是基于buffer header被触及的次数来确定的。注意FTS(全表扫描)窗口的概念将不再需要并且已经被删除了。touch-count算法有三个关键点:midpoint-insertion,touch count incrementation与buffer promotion

Midpoint Insertion
与修改后的LRU算法最根本的背离是midpoint insertion。每个LRU chain被分成hot区与cold区。当一个buffer从磁盘被读入且找到了一个free buffer,这个buffer与buffer header将替换之前的buffer与buffer header的内容然后这个buffer header被移动到LRU chain的midpoint。单块读,多块读,快速完全索引扫描或全表扫描都没有差别。buffer header不会被插入到LRU chain的MRU端,而是LRU chain的midpoint。这确保了不会因为单个对象的大量数据块被读入到buffer cache中而使用LRU chain被破坏掉。

缺省情况下,hot区与cold区各占一半。midpoint确实在中间。然而这个可以通过隐含参数_db_percent_hot_default来配置。

当其它buffer headers被插入到midpoint或被promoted(提升)时,原有的buffer headers自然地将从LRU chain的hot区移动到cold区。在一个buffer header被插入后,只有一种方式可以保留在cache很长时间就是被不断重复地promoted。

因为窗口方案用于修改的LRU算法中而不再被使用,隐含参数_small_table_threshold因此被丢弃。然而在Oracle11g中,它又再次被使用,但是用于不同的目的。从Oracle 11g开始,_small_table_threshold参数是服务器进程开始执行直接路径读的阈值。直接路径读可以提高性能因为数据块从磁盘直接读取到服务器进程的PGA内存中而不用放入buffer cache。然而,这是更自私的读取操作并且可能实际上降低性能,因为其它的服务器进程不能从IO操作中获利。

SQL> select x.ksppinm NAME,y.ksppstvl value,x.ksppdesc describ
  2  from x$ksppi x, x$ksppcv y
  3  where x.inst_id=USERENV('Instance')
  4  and y.inst_id=USERENV('Instance')
  5  and x.indx=y.indx
  6  and x.ksppinm like '%&par%';
Enter value for par: _small_table_threshold
old   6: and x.ksppinm like '%&par%'
new   6: and x.ksppinm like '%_small_table_threshold%'

NAME                           VALUE                           DESCRIB
------------------------------ ------------------------------  -----------------------------------------------------
_small_table_threshold         60283                           lower threshold level of table size for direct reads

假设你是一个服务器进程必须要查询一行存放在特定数据块中的记录。基于这个SQL语句与数据字典,你知道数据块的文件号与块号。如果只关心查询速度,因此希望这个数据块已经存放在buffer cache中了。为了检查数据块是否存放在buffer cache中,需要得到buffer***217;s buffer cache内存地址,它存放在它的buffer header中。

为了找到buffer header,必须访问CBC结构。哈希文件号与块号,它将指向一个哈希桶。基于这个哈希桶,可以查找相关的CBC latch与持有它。在几次spin后,你可能可以获得latch,因此开始你的序列化CBC搜索。第一个buffer header如果不是你想要的,并且不幸地是在这个CBC中没有第二个buffer header,因此知道buffer当前没有放入buffer cache。

释放CBC latch并执行调用给操作系统,要求访问你需要的数据块。当你正等待时,你将被告知db file sequential read等待事件。最终从操作系统接收到这个数据块并在PGA中持有它。因为没有使用直接路径读,在你或其它服务器进程访问buffer之前,buffer必须被合理地插入到buffer cache并更新所有合理结构。

你将需要一个free buffer用来在buffer cache中存放刚读取的数据块,因此你将移到LRU chain的LRU端。但在你开始扫描LRU chain之前,你必须持有并获得相关的LRU chain latch。之后当休眠时通过spinning与posting等待事件latch:cache buffers lru chains来消耗CPU,最终获得latch。从LRU chain的LRU端开始,你查看buffer header是否它是一个不被频繁访问的free buffer,得到的回答是它是不被频繁访问的buffer。那么你现在就可开始buffer替换操作。你立即pin(固定)住这个buffer header。从buffer header中,可以获得数据块对应buffer在buffer cache中的内存地址,使用刚被读取的且仍在你PGA内存中的块来替换这个free buffer,执行任何要求buffer header所要进行的修改。你维护这个LRU chain并移动buffer header到LRU chain***217;s midpoint,释放LRU chian latch,并unpin这个buffer header。现在任何服务器或后台进程包括你可以访问这个buffer,这将都是在一瞬间就能完成。

Touch Count Incrementation
这个概念是一个buffer header每被touch一次,它的touch count将会增加。事实上并不是这样。缺省情况下,一个buffer header的touch count只有每3秒才会增加一次。这可以用来确保buffer活动时间超过几秒才算做被频繁访问

当一个buffer被插入到buffer cache中时,它的touch count被设置为0.然而,如果buffer在短期内被重复地touch,那么touch将不会进行计数。

Oracle也允许touch count被遗漏。这将没有latch被调用(这是消除latch竞争最好的方法),并且Oracle不会pin住buffer header。不使用序列化控制,两个服务器进程可以递增与更新buffer header***217;s的touch count到相同的值。

假设服务器进程S100在时间T0点得到的buffer header的touch count是13,并且开始递增为14。但服务器进程S200现在在时间T1点询问这个buffer header的touch count,并且因为服务器进程S100还没有完成touch count的递增操作,所以buffer header的touch count现在仍然显示为13。服务器进程S200现在开始将touch count从13递增到14。在时间T2点,服务器进程S100将buffer header的touch count修改为14,并且在时间T3点,服务器进程S200也将buffer header的touch count修改为14。这是不是touch count递增被遗漏了?没有结构被损坏,并且touch count确实已经被递增了,但不是递增两次。如果一个buffer确实被频繁地访问,它将再次被touch。通过这种模糊实现节省的是CPU的消耗与内核代码运行量。

Buffer Promotion
没有说当一个buffer被touch后,它将会被promoted到LRU chain的MRU端。这是因为buffer header的touching与buffer header的promotion现在是两个分开的操作。当一个buffer被考虑进行promotion时,也会考虑替换它。而服务器进程与数据库写进程都可以promote buffer header,但只有一个服务器进程将替换这个buffer并且与它相关的buffer header作为一个物理读取数据块的结果。数据库写进程执行替换没有意义,因为它没有替换的内容。

在一个服务器进程从磁盘读取一个数据块之后,它必须要找到一个不被频繁访问的free buffer来存放刚被读取的数据块。服务器进程要获得适当的LRU latch,然后从LRU chain的LRU端开始扫描buffer headers。记住buffer headers存放在LRU chain中,不是buffers中。如果服务器进程遇到了一个free buffer header,那么它检查它是否被频繁访问。如果被频繁访问,服务器进程将promote这个buffer header,然后继续扫描。如果这个free buffer header不被频繁访问,服务器进程将使用从磁盘读取到的数据块来替换这个buffer,并更新buffer header,移动buffer header到LRU chain的midpoint。注意这里不需要更新CBC结构,因为buffer没有被移动,只有LRU chain上的buffer header被移动。如果服务器进程遇到一个dirty buffer header,那么检查是否是一个被频繁访问的dirty buffer header。如果dirty buffer header被频繁访问,它将promote这个buffer header并继续扫描。如果dirty buffer header不被频繁访问,服务器进程将移动这个buffer header到写列表中。如果服务器进程遇到一个被pin 住的buffer header,那将继续扫描。pin住的buffer被禁止使用。

promotion操作只要达到最低值2(_db_aging_hot_criteria)就会中断。因此当一个服务器进程或数据库写进程在询问“每个buffer的touch count数是多少?***221;时,它实际是问“buffer的touch count是否大于或等于_db_aging_hot_criteria?”。如果每隔几秒一个buffer就会被touch,那么它应该被保留在cache中。如果不是,它将被快速替换掉。

当一个被频繁访问的buffer被promoted时,它的生命周期将变得更困难。promotion操作的一部分是touch count被设置为0(_db_aging_stay_count)。除非buffer是一个segment header或一个consistent read(CR) buffer,否则会出现这种情况。

SQL> select x.ksppinm NAME,y.ksppstvl value,x.ksppdesc describ
  2  from x$ksppi x, x$ksppcv y
  3  where x.inst_id=USERENV('Instance')
  4  and y.inst_id=USERENV('Instance')
  5  and x.indx=y.indx
  6  and x.ksppinm in('_db_aging_stay_count');

NAME                      VALUE        DESCRIB
------------------------- ------------ --------------------------------------------------------------
_db_aging_stay_count      0            Touch count set when buffer moved to head of replacement list

数据库写进程也可能promote被频繁访问的buffer headers。当一个数据库写进程处于休眠状态,它将每3秒钟被唤醒一次。每个数据库写进程都有一个属于它的写列表(dirty列表)并且它也与一个或多个LRU chain相关联。当一个LRU chain的数据库写进程被唤醒,它将检查它的写列表来查看写列表的长度是否足够执行一个IO写操作。如果数据库写进程决定构建一个写列表,它将扫描它的LRU chain来查找不被频繁访问的dirty buffer。非常像服务器进程查找free buffer那样,数据库写进程也将获得相关的LRU chain lath,从LRU chain的LRU端开始并检查buffer header是否为dirty且不被频繁访问。如果一个不被频繁访问的dirty buffer被找到,数据库写进程将会这个buffer header从LRU chain移动到它的写列表中(记住,这个buffer header仍然存放在CBC结构中,因此它能被其它进程找到)。如果写列表的长度仍然不足够执行一次IO写操作,那么数据库写进程将继续扫描它的LRU chain,查找更多的不被频繁访问的dirty buffer headers。

Hot Region to Cold Region Movement
一个buffer header的生命周期在LRU chain是从midpoint(正中间)开始的。因为其它buffer headers将被替换并且被插入到正中间,随着buffers被promoted,一个buffer header自然地将迁移到LRU chain的LRU端。promote一个buffer header的唯一方法就是buffer header标识为被频繁访问。当一个buffer跨过正中间(midpoint)时另一个显著事件会出现,那就是从hot region移动到cold region。

当一个buffer进入到cold region中时,它的touch count会被重设置为缺省值1(_db_aging_cool_count)。这有冷却hot buffer的效果,任何希望保留在cache中的buffer都不想出现这种情况。增加这个参数值将人为增加buffer值从而增加了buffer移动的可能性。因此缺省情况下,当一个buffer header进行到cold region时,它必须至少被touched一次来使其匹配promotion操作的条件(_db_aging_hot_criteria)。

SQL> select x.ksppinm NAME,y.ksppstvl value,x.ksppdesc describ
  2  from x$ksppi x, x$ksppcv y
  3  where x.inst_id=USERENV('Instance')
  4  and y.inst_id=USERENV('Instance')
  5  and x.indx=y.indx
  6  and x.ksppinm in('_db_aging_cool_count');

NAME                      VALUE     DESCRIB
------------------------- --------- ------------------------------------
_db_aging_cool_count      1         Touch count set when buffer cooled


SQL> select x.ksppinm NAME,y.ksppstvl value,x.ksppdesc describ
  2  from x$ksppi x, x$ksppcv y
  3  where x.inst_id=USERENV('Instance')
  4  and y.inst_id=USERENV('Instance')
  5  and x.indx=y.indx
  6  and x.ksppinm in('_db_aging_hot_criteria');

NAME                       VALUE      DESCRIB
-------------------------- ---------- ---------------------------------------------------------------
_db_aging_hot_criteria     2          Touch count which sends a buffer to head of replacement list

Touch Count Changes
可能会疑问为什么当一个buffer header被promoted和当它进入到cold region时Oracle要重新设置touch count。要理解这一点关键要理解中间点(midpoint)。中间点(midpoint)缺省情况下将每个LRU chain平分为hot与cold
region(_db_percent_hot_default=50),它可以被设置为0到100之间的任何数值。如果LRU chain变成一个100%的hot region,那么唯一的touch count重置将发生在buffer被promoted时。当Oracle释放出创建任何数量buffer pools的能力时,在每个pool中维护中间点(midpoint)的能力将允许高度优化和特定的LRU活动。尽管双重设置可能最初看起来比较愚蠢,但它确实有其真正的目的并为将来奠定了基础。

SQL> select '00 : '||count(*) x from x$bh where tch=0
  2  union
  3  select '01 : '||count(*) x from x$bh where tch=1
  4  union
  5  select '02 : '||count(*) x from x$bh where tch=2
  6  union
  7  select '03 : '||count(*) x from x$bh where tch=3
  8  union
  9  select '04 : '||count(*) x from x$bh where tch=4
 10  union
 11  select '05 : '||count(*) x from x$bh where tch=5
 12  union
 13  select '06 : '||count(*) x from x$bh where tch=6
 14  union
 15  select '07 : '||count(*) x from x$bh where tch=7
 16  union
 17  select '08 : '||count(*) x from x$bh where tch=8
 18  union
 19  select '09 : '||count(*) x from x$bh where tch=9
 20  union
 21  select '10 : '||count(*) x from x$bh where tch=10
 22  union
 23  select '11 : '||count(*) x from x$bh where tch=11
 24  union
 25  select '12 : '||count(*) x from x$bh where tch=12
 26  union
 27  select '13 : '||count(*) x from x$bh where tch=13
 28  union
 29  select '14 : '||count(*) x from x$bh where tch=14
 30  union
 31  select '15 : '||count(*) x from x$bh where tch=15
 32  union
 33  select '16 : '||count(*) x from x$bh where tch=16
 34  /

X
---------------------------------------------
00 : 1879125
01 : 697463
02 : 254482
03 : 227324
04 : 161410
05 : 141651
06 : 91699
07 : 70599
08 : 55605
09 : 25551
10 : 17181
11 : 29833
12 : 19978
13 : 13324
14 : 29006
15 : 9998
16 : 9649

17 rows selected

touch count被重新设置有重要影响。首先,这意味着touch count不会飙升到无穷大。touch count重新设置也意味着最被频繁访问的buffer headers将不需要有最高的touch counts。如果你注意到一个特定的buffer有一个较低的touch count,那么你可能捕获了一个被频繁访问的buffer,只是它可能刚刚被promoted或进入到LRU chain的cold region。事实上,最高touch count的buffer headers将存放在LRU chain的LRU端附近。

LRU Chain Contention Identification and Resolution
Oracle的LRU touch-count算法,与缺省的实例参数设置进行组合来使用微不足道的竞争来启用高性能LRU chain活动。当touch-count算法遇到压力时,这是IO和CPU活动的独特组合。

LRU chain latches命名为cache buffers lru chain。哈希chain latches被命名为cache buffer chains。命名很接近并且可能导致相当大的混乱。只要记住LRU chain latches的名字中lru就不会混乱。在Oracle 10g之前的版本中,等待事件被简化成latch free,为了判断特定的latch,需要使用v$session_wait视图中的p2列与v$latch中的latch#进行关联来进行查询。对于Oracle 10g及以后的版本,等待事件标识为latch:cache buffers lru chain。

如果不需要执行物理读来从磁盘读取数据,那么就不会存在LRU chain latch竞争,因为就不需要查找free buffer或者插入一个buffer header到一个LRU chain中。数据库写进程查找不被频繁访问的dirty buffers不会对LRU chain结构造成压力从而导致LRU chain latch的竞争。然而,任何时候一个服务器进程从磁盘读取数据块,它必须要找到一个free buffer,这将请求LRU chain活动(除了直接路径读)。如果IO读区花了10ms,那么你可能看到的是db file scattered read与db file sequential read等待事件而不是LRU chain latch竞争。但如果IO子系统返回数据块的时间少于5ms,那么压力就转移到CPU子系统了,并且这时LRU chain的活动将开始承受压力。

LRU chain latch竞争可能的结果是获取latch的问题,持有latch大长时间或者两个同时出现。如果操作系统的CPU受限,获得latch可能花费很长时间,因为没有足够的CPU周期。一旦latch被获得且LRU chain相关的内核代码被运行,如果CPU周期供应不足或者不被频繁访问的free buffers有限,LRU chain latch可能被持有很长时间足够造成严重的竞争。

因此,首先,必须要有强烈的物理读取活动。第二,IO子系统响应时间非常快,将大部分的等待时间从读取等待事件传递到LRU chain latch等待事件。这种竞争提供了许多可供组合使用解决方法:
.优化物理IO SQL语句
如果没有物理IO存在就不会有大量的LRU chain latch竞争。因此,从应用程序角度来说产,查找主要活动为执行物理块读取也就是物理IO活动的SQL语句。尽你所能地减少SQL语句的物理IO消耗。这意味着执行经典的SQL优化操作,包括使用索引,以及在性能关键时期减少顶级物理IO SQL语句的执行速度。

.增加CPU处理能力
与CBC latch竞争一样或任何其它latch竞争一样,如果有更多的CPU资源可以使用,内存管理将会花费更少的时间。这意味着latch持有时间与latch获取时间(spinning与sleeping)将被减少。增加CPU处理能力也意味着在竞争高峰期间寻找创建性方法来减秒CPU消耗。

.增加LRU latch数量
通过增加latches可以增加LRU的并发,这意味着增加隐含参数_db_block_lru_latches的值。如果有很多G的buffer cache增加latches可能是特别有效的。

.使用多个buffer pools
一种创造性策略来减少主LRU chain压力的方法就是实现keep与recycle pools。所有的buffer pools都可以增加LRUchain latches的数量。它们也使用touch-count算未能,并且有类似的touch count实例参数,比如_db_percent_hot_keep

.调用touch count实例参数
有几个可用touch count参数。但要注意,这些参数的值都很小,比如1和2。因引,即使参数从1修改为2都是相当大的改变可能导致意想不到的后果。只有在测试后将调整touch count参数作为最后的手段。

_db_percent_hot_default参数,它的缺省值为50。它表示在hot region的buffer headers的百分比。如果想要更多的buffer header存放在hot region,可以增加这个参数。减小这个参数将会给予buffer headers在遇到一个服务器进程或数据库写进程之前更多的时间来被touched。

_db_aging_touch_time参数,它的缺省值为3它是唯一能增加一个buffer header的touch count(x$bh.tch)时间窗口的方法。增加这个参数将减小突然爆发以buffer为中心活动的影响,同时会冒着贬值频繁被访问buffer的风险。

_db_aging_hot_criteria参数,它的缺省值为2。一个buffer header的touch count阈值必须满足或被超过才能被promoted(提升)。如果想一个buffer被promoted更困难,可以增加这个参数值。那么只有真正hot buffers才会被保留在cache中。

_db_aging_stay_count参数,它的缺省值为0。当一个buffer header被promoted时touch count被重设置后的值。一致性读与段头块除外。

_db_aging_cool_count参数,它的缺省值为1。当一个buffer header从hot region进入cold region时touch count被重设置后的值。减小这个参数值将使buffer header被promoted变得更困难。

_db_aging_freeze_cr参数,它的缺省值为false。使一致性读取的 buffers总是为cold状态,因此它们容易被替换。

Oracle Cache Buffer Chains

一个Oracle Buffer是一个Oracle段对象缓存块。一个Oracle buffer一开始时包含与Oracle块中相同的信息。一个buffer的内容依赖于段类型以及它是滞是一个段头块。buffer有许多种状态通过v$bh的state列来表示,它们可能被归纳成在种模式:free(可用),dirty(脏)与pinned(固定)。

Free Buffers
当一个buffer与磁盘上的数据块匹配时它的状态就是free。一个free buffer可以看作是一个镜像buffer,因为它镜像了磁盘上的数据块。下面的查询简单的显示了如何判断buffer cache中free buffers的数量。一个free buffer可能确实是空的(例如,在实例重启之后),但它将最有可能包含真实的块信息,比如行记录。一个free buffer可以被替换而不会产生任何损坏,因为有一个副本存储在磁盘上。当然,如果一个事务提交,那么至少被修改的buffer必须被记录到联机重做日志文件中。

SQL> select count(*) from v$bh where status='free';

  COUNT(*)
----------
        24

一个free buffer可能不是被频繁的访问。也许一个查询需要访问单行数据因此需要将数据块放入buffer cache中,而这个buffer之后再也没有被访问过。而另一方面,一个free buffer也可以是被频繁访问的。例如,如是一个特定的数据块被重复地查询,它将被频繁的访问,但它的状态仍然是free状态,因为buffer没有被改变过。如果你对freebuffer的定义简单又清晰,那么许多Oracle的算法将也变得清晰,这将使理解,检测与解决竞争更容易。

Dirty Buffers
当一个buffer它不能与磁盘上的相关块进行匹配时它的状态就是dirty。对一个buffer进行的任何改变都会使用它的状态变为dirty,因为buffer将不再与磁盘上的块相匹配。当内存中的改变还没有被写入磁盘而要对其进行覆盖时,dirty块是不能被替换的。一旦数据库写进程将一个dirty buffer写入磁盘,那么buffer将与磁盘上的块再一次匹配那么这个buffer的状态将变为free。

一个dirty buffer可能也不被频繁访问。假设一行记录被修改但其它进程不需要访问这个buffer。因为行记录被改变这个块确实是dirty的,但它不被频繁访问。当然,也有被频繁访问的dirty buffers。简单地重复更新一行记录将确保它的buffer的状态为dirty又被频繁的访问。

下面的查询显示dirty buffers的状态可能是xcur或write。将在cache buffer chains中详细介绍current与consistent模式的buffers。xcur状态意味着一个进程已经改变了一个current模式的buffer的状态为这种状态,并且进程可能现在更新buffer中的行记录,虽然行记录现在仍然受制于其它条件,比如行级锁。排他模式不会阻止多个用户改变相同buffer中的多行记录,它简单表示当current模式的buffer可以被改变。在RAC环境中这是至关重要的,可能有多个共享current模式buffers(scur),但在整个RAC数据库中每个块只有一个排他current模式buffer存在。

SQL> select status, count(*) from v$bh where dirty='Y' group by status;

STATUS       COUNT(*)
---------- ----------
xcur            20792
scur              919
pi               2567

Pinned Buffers
当一个buffer被pinned时,它不能被替换。另一种看待pinning的方式是对buffer的一种非官方锁。因为一个buffer不是一种关系结构,标准的锁机制不能应用。Pinning一个特定的buffer,latches或mutexes可以控制访问整组buffers。Pinning可以与latch与lock一起连用来确保适当的序列化,保护与并行控制被实现。

假设一个服务器进程将要读取一个buffer中的一行记录。当你仍然在访问这一行记录时,有人使用其它的buffer替换了你正在访问的buffer这是极端粗鲁的。这就像你正在读一本书时,有一个人说***221;让我看看***221;,并从你手中抢走一样。许多进程可以pin相同的buffer(读取相同的块),但是只有一个进程能pinned这个buffer,它不能被替换。当一个free buffer的行记录正被查询时,它的状态从free变为pinned再次回到free。当free buffer中的行记录被修改后,它的buffer状态将从free变为pinned,再变为dirty。

Oracle没有通过v$bh视图来显示pinned buffers,但任何被touched的buffer也就是被pinned了。当一个buffer正被移动到写列表中并且正更新touch计数时Oracle将也会pin这个buffer。

Buffer Headers的作用
当buffers内置在buffer cache中并且buffers确实已经被改变了,列表管理实际作用于buffer headers,而不是实际的buffers。一个buffer header是一个优化过的内存结构它包含关于一个buffer和与它相关的块信息,但不包含块数据比如行记录。

为什么对于buffer cache没有视图v$bc?,这是因为一个buffer与一个块的元数据被存储在buffer header,并且它的元数据对于我们的性能分析是需要的。因此视图被命名为v$bh,对于buffer header有三个关键的列表或链:
.Cache buffers chains(CBCs)被用来快速判断一个Oracle块是否内置在buffer cache中。

.最近最少使用(LRU)列被用来在cache中保留住被频繁访问的buffers并找到free buffers。

.写列表包含不久将被写入磁盘的dirty buffers。

重要的是理解buffer headers的这三个列表而不是实际的buffers。单个buffer header总是内置在一个CBC中和一个LRU链或一个写列表中。

三个列表的维护是在buffer header级别,不是buffer级别,更不是在数据块级别。我们许多人被教导当buffer内置在buffer cache中时,buffers它们本身是被链接的。这是不正确。每个buffer都与一个buffer header相关联,并且在各种列表中操作的是buffer header。

Cache Buffer Chains
简而言之,CBCs被用来回答“这个buffer是否在buffer cache中,如果在,它在哪里”这本质上是一个搜索类型的问题。很多类型的搜索算法可能被用来获得答案:二叉树,B+树,B*树,顺序搜索,哈希算法,或一些算法组合。Oracle选择使用一种哈希算法,紧接着使用快速顺序搜索。

哈希算法
哈希算法可以非常快速,因为整个结构通常被存储在内存中并且要求一个单独的数学计算,同时存在一些内存访问来回答搜索问题。哈希结构有许多变化,但所有的哈希结构都是由一个哈希函数,哈希桶与哈希链组成的。

哈希函数
哈希函数接收输入并使用定义的范围来产生一个输出。输入被叫作一个哈希值。x mod 10函数可以简单地被用来确保不管输入的正数哈希值,它的输出总是在0到9之间。哈希值输入11,输出将是1。一个好的哈希函数将会产生均匀分布的输出。当Oracle将要搜索一个buffer时,基于数据块的文件号与块号的组合(它也叫数据块地址DBA)来生成一个哈希值。因此哈希函数本质上是对buffer的数据块文件号和块号进行哈希运算。这是一种非常方便并可以快速哈希运算的情况。

哈希桶
哈希输入值将被哈希到桶,每个输出值代表了一个单独的桶。在许多哈希情况下,可能输入的哈希值的数量超过桶数。对于Oracle来说,可能的哈希输出值就是Oracle数据块的数量。但在任何情况下,哈希输入值的数量将与buffercache中的buffers的数量相等。

当有两个哈希值被哈希到相同的桶时,这叫作碰撞。碰撞对于哈希来说是很常见的。碰撞可以通过增加哈希桶的数量来最小化,但可能对于高性能程序来说是一种灾难,比如Oracle数据库。例如,假设x mod 10的哈希函数有1000哈希输入值,这将肯定会出现碰撞。为了避免碰撞,哈希算法输出完全均匀的输出将需要1000个哈希桶。使用一种极好的哈希算法与大量的哈希桶两种方法减少碰撞。如果哈希算法不变,那么可以增加哈希桶的数量。

哈希链
每个哈希桶都有一个相关联的哈希链。当一个搜索的对象被哈希到一个桶时,这个桶的链被顺序搜索来查找对象。如果对象在哈希链中没有找到,我们知道对象不在整个哈希结构中。如果哈希链很短,顺序搜索将很快完成。如是对象不在cache中,链长度最好为零。

Oracle的CBC结构是一种复杂的内存结构,并且Oracle必须要维持序列化控制。所以它使用了一种序列化结构:latch或mutex。

如何破坏CBC的性能
要学习如何解决性能问题的最好方法就是知道如何模拟问题,有三种典型的方法来降低CBC的性能:
.当减少latches的数量时,剩余latches的并发将会增加
.如果减少CBCs的数量,平均每个CBC的长度将会增加,剩余chains的并发与CBC的扫描时间也会增加
.如果buffer克隆变得激烈,那么频繁访问的chain将变得很长,会增加并发与CBC的扫描时间

减少latches来限制并发
使用单个latch,序列化将被保证,但是并发性将受到严重的限制。当另一个进程请求latch时而它被其它进程所持有时就会产生竞争。在这个例子中,简单地增加一个latch可以解决这个问题。如果存在上百成千个进程需要访问CBCs,那么可以看到存在严重的并发性能限制问题。幸运地是缺省情况下Oracle创建了上百个CBC latches。

Oracle知道它的哈希函数不完美并且将会产生碰撞。一种减少碰撞的方法是有大量的CBCs。但你第一反应会觉得更多的CBCs将会消耗更多的内存,但事实不是这样的。每个buffer header必须内置在一个CBC链上,与CBC链的数量及长度无关。当使用更多的CBC链时,而buffer headers的数量不变时,平均CBC链的长度会减小。因此,对于每个CBC链虽然有一些额外的内存消耗,但真正的内存消耗者是buffer headers的数量,不仅仅是CBC链的数量。

许多年以前规则定义latches的数量不应该超过CPU核数的两倍。很明显Oracle已经修改了规则,CBC latches只是Oracle数据库中许多latches中的一种。

Oracle可能处理多个CBC latches,有人会认为对于每个CBC将有一个latch,但Oracle认为这是不必要的且一个latch可以管理上百个CBC链。

如果CBC链比buffers多,这意味着有一些CBC链将不会关联buffer header,这将有效的使CBC链的长度变为零。

[oracle@jytest2 ~]$ sqlplus / as sysdba

SQL*Plus: Release 12.2.0.1.0 Production on Thu Mar 21 10:28:02 2019

Copyright (c) 1982, 2016, Oracle.  All rights reserved.


Connected to:
Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production

SQL> col param format a50 heading "Instance Param and Value" word_wrapped
SQL> col description format a20 heading "Description" word_wrapped
SQL> col dflt format a5 heading "Dflt?" word_wrapped
SQL> select rpad(i.ksppinm, 35) || ' = ' || v.ksppstvl param,
  2  i.ksppdesc description,
  3  v.ksppstdf dflt
  4  from x$ksppi i,
  5  x$ksppcv v
  6  where v.indx = i.indx
  7  and v.inst_id = i.inst_id
  8  and i.ksppinm in
  9  ('db_block_buffers','_db_block_buffers','db_block_size',
 10  '_db_block_hash_buckets','_db_block_hash_latches'
 11  )
 12  order by i.ksppinm
 13  /

Instance Param and Value                           Description          Dflt?
-------------------------------------------------- -------------------- -----
_db_block_buffers                   = 97136        Number of database   TRUE
                                                   blocks cached in
                                                   memory: hidden
                                                   parameter

_db_block_hash_buckets              = 262144       Number of database   TRUE
                                                   block hash buckets

_db_block_hash_latches              = 8192         Number of database   TRUE
                                                   block hash latches

db_block_buffers                    = 0            Number of database   TRUE
                                                   blocks cached in
                                                   memory

db_block_size                       = 8192         Size of database     FALSE
                                                   block in bytes

引起CBC latch竞争的最好和最简单的方法之一就是创建一个大的buffer cache来缓存更多的块,然后将CBC latches的数量减少到一个。Oracle从10g开始就不允许CBC latches的数量小于1024,但是即使有1024个CBC latches和足够的逻辑IO能力,也能经常看到CBC latch竞争。

通过减少CBC的数量来增加CBC的扫描时间
如果CBCs很长,那么扫描它的时间将会引起显著的竞争。另外其它进程获得CBC latch的时间也会显著增强。一种很明显的方式是增加平均每个CBC的长度来减少CBC的数量,这可以通过减少哈希桶的数量来完成。简单地将实例参数_db_block_hash_buckets减少到50,确保你查询的块内置在buffer cache中,那么会很快得到CBC latch竞争。因为Oracle至少要确保64个哈希桶来忽略你的设置,但这仍然会有大量的竞争。

在现实中,一种解决CBC latch竞争的方法是增加哈希桶的数量,这将减少平均每个CBC的长度。如果一个特定的CBC很长且被频繁文章,那么这个解决方案将不能提高性能。此外Oracle创建了大量的CBC,因此增加哈希桶的数量不像增加CBC一样能显著的提高性能,但它有一种有效的方法应该值得考虑。

使用克隆Buffers来增加CBC的扫描时间
虽然长CBC的问题很少见,但如果出现了,那么情况是很严重的。理解这是如何发生的不仅仅可以帮助你解决这个问题还能更深入的理解CBCs,latch,undo与读一致性。它涉及RAC系统。

长CBC代表了一个非常有挑战性的问题。首先,哈希结构是很快速的因为几乎没有扫描,因此长CBC会迅速降低使用哈希算法的好处。第二,一个扫描进程必须处理一个CBC latch,不是随便一个CBC latch,这个CBC latch保护特定的CBC。一个长CBC意味着CBC latch将被持有更长时间并且当扫描列表将使用更多的CPU。另外,因为CBC latch被持有的时间更长,这将增加另外的进程竞争latch的可能性。当竞争latch的进程在spinning与在sleeping时发布等待事件时都是要消耗CPU的。但问题远不止如此。

正常情况下,Oracle的哈希算法使用的CBC的数量是buffers的两倍还多,因此CBC的长度很短。长CBC出现的唯一方式是多个buffers被哈希到相同的CBC上。通常这不是一个问题,但也可能出现。为了解析这种情况,先了解块克隆与哈希。当一个Oracle块被cached后,只有单个当前模式buffer能被修改。如果buffer中的一行需要被修改,单个当前模式buffer必须是可用的。当前模式buffers有时也叫CU buffers。在RAC系统中,如果需要的当前模式buffer内置在另一个实例中,那它必须被发送到你使用的这个实例中然后才可以修改buffer。

假设一个服务器进程在时间T100正运行一个查询。这个进程访问数据字典并知道它将必须访问一个特定块,因此它将被哈希到合适的CBC,获取合适的CBC latch,扫描CBC,并找到当前模式buffer的buffer header。然而在检查buffer header时,发现当前模式buffer在时间T200被修改过,是在服务器进程开始执行查询之后。这意味着在查询执行后需要的行记录已经被修改过了。 Oracle的缺省读一致性模式要求被返回的信息与查询开始执行时的一致。因此Oracle必须采取操作来确保被返回的信息对于时间T100来说是正确的。

Oracle现在要么找到一个buffer的副本,要么构建一个当前模式buffer的副本,因此这个buffer代表了时间T100所处处的情况。一个buffer副本通常叫做buffer克隆。克隆一个buffer是一种相对昂贵的处理。首先,必须找到一个free buffer,然后buffer header必须被合适的连接到CBC结构与LRU链结构。

理解潜在的重大性能影响的关键是理解被克隆的buffer的buffer header将内置在CBC结构中的什么位置。因为被克隆的buffer是一个合法的buffer,它在buffer cache中占据了空间,能被共享且必须被定位。这意味着它必须被合适的内置在CBC结构中。被克隆的buffer的文件号与块号与它的当前模式buffer的相同,这意味着它必须被哈希到相同的CBC。因此,如果一个buffer有50个克隆副本,与它相关的CBC将至少有50个buffer header那么长,并且如果与其它buffer出现碰撞可能更长。Oracle对此无能为力,因为哈算法是基于文件号与块号的。

不仅free buffer搜索算法有利于替换克隆的buffer,但Oracle试图限制每个buffer的克隆数量。Oracle想要每个buffer的克隆数量不超过隐含参数_db_block_max_cr_dba,它的缺省值为6。然而如果克隆变得很激烈,一个buffer的克隆副本很容易超过6个。

SQL> col name for a30
SQL> col value for a20
SQL> col describ for a50
SQL> select x.ksppinm NAME,y.ksppstvl value,x.ksppdesc describ
  2  from x$ksppi x, x$ksppcv y
  3  where x.inst_id=USERENV('Instance')
  4  and y.inst_id=USERENV('Instance')
  5  and x.indx=y.indx
  6  and x.ksppinm like '%&par%';
Enter value for par: _db_block_max_cr_dba

NAME                           VALUE                DESCRIB
------------------------------ -------------------- --------------------------------------------------
_db_block_max_cr_dba           6                    Maximum Allowed Number of CR buffers per dba

1 row selected.

有许多克隆的buffer不一定意味着有性能问题。如果真的出现性能问题,CBC latch竞争问题将非常明显。如果出现这种情况并发现克隆buffer的问题,那么考虑以下可能的补救措施:
.修复应用程序
这通常是必须要做的。这是非常痛苦的,需要开会,如果应用程序开发者参与将会非常专业化,并且通常要求应用程序以某些方式被修改来减少单个克隆buffer被频繁的访问。

.移动行记录
如果幸运的话,可能存在多行记录使得buffer被频繁访问。如果可能散这些行,因此多个buffer现在不再被频繁的访问。当修改传统的pct_free与pct_used存储参数是一种选择时,为了增加控制,可以考虑设置一个块可以存储的最大记录数。意外地是这不仅仅是简单地执行类似于alter table all_status minimizer records_per_block 5语句

.平衡工作负载
如果能控制工作负载强度,在克隆活动高峰期间,考虑减少与buffer克隆活动相关的工作负载。虽然这不是一个令人兴奋的解决方案,工作负载平衡也能对性能产生积极影响。

CBC竞争识别与解决方案
一些解决方案可以帮助你解决CBC竞争的问题。在尝试解决CBC latch问题之前,确保它们存在。

SQL> @swpctx
Remember: This report must be run twice so both the initial and
final values are available. If no output, press ENTER twice.

DB/Inst: RLZY/RLZY1                                               25-Mar 11:24am
Report:   swpctx.sql           OSM by OraPub, Inc.                Page         1
            System Event CHANGE (17 sec interval) Activity By PERCENT

                                       Time Waited  % Time    Avg Time     Wait
Wait Event Display Name                      (sec)  Waited Waited (ms) Count(k)
-------------------------------------- ----------- ------- ----------- --------
latch: cache buffers chains                 10.610   96.28        15.7        1
control file parallel write                  0.160    1.45         7.6        0
log file parallel write                      0.030    0.27        15.0        0
log file sync                                0.000    0.00         0.0        0

如果数据库系统是Oracle 10g之前的版本,那么top wait event将会是latch free,就需要确认latch问题是CBClatch。对于Oracle 10g及以后的版本,wait event将是latch: cache buffers chains。在大多数情况下,CPU子系统将被大量利用并且负担过重。以下是可能的CBC latch解决方案:
.优化逻辑IO SQL语句
当回答“buffer是否在buffer cache”中时CBC结构将变得紧张起来,期待的答案总是“Yes”,如果答案为“No”,将会看到顺序读或分散读等待事件。因此从应用程序角度来看,查找执行活动主要是buffer gets也就是逻辑IO的SQL尽你所能地减少逻辑IO消耗。这是典型的SQL优化,包括索引,以及在性能问题出现时减少执行速率。

.增加CPU处理能力
在大多数情况下,CPU子系统将被过多利用并且可能是操作系统瓶颈。latch的获得与相关的内存管理可能消耗过多的CPU资源。做任何可以减少CPU消耗与能增加CPU能力的事。查找在高峰期间没有执行或正在执行的进程。考虑增加或者使用更快的CPU。如果正在运行在虚拟环境中,考虑确保Oracle系统已经增加CPU资源。然而,请注意除非应用程序工作负载已经显著增加,增加的CPU处理能力通常将被快速地消耗掉。真正的解决方案可能是其它的方案。增加CPU能力可能是一个快速解决方案,但它可能不能真正地解决问题。

.检查buffer克隆问题
无论何进遇到CBC latch竞争问题,都需要检查是否存在buffer克隆的问题。这是很少见的情况,但如果遇到了,那么解决方案与其它解决方案是非常不同的。

.增加CBC latch数量
这通常会带来一些安慰,但不是真正的优化逻辑IO SQL。隐含参数_db_block_hash_latches控制着CBC latch的数量

.增加CBC buckets
它很难对性能产生影响,因为Oracle缺省情况下,创建了大量的buckets。除非之前减少了CBC buckets的数量,增加这个参数的大小将会显著地影响性能。

In-Memory Column Store

使用In-Memory Column Store
从Oracle 12.1.0.2开始可以使用In-Memory Column Store。In-Memory Column Store对于SGA是一个可选部分用来存储表,表分区与其它数据库对象副本。在In-Memory Column Store中,数据是以列式被加载而不是像SGA中其它部分的数据是以行式被加载,并且数据被优化用于快速扫描。

In-Memory Column Store在SGA中是一个新的静态内存池。在In-Memory Column Store中所存储的数据不是使用传统行格式而是使用列式。每个列作为单独的结构进行存储。In-Memory Column Store不会替代buffer cache,是对buffer cache的一种补充,因此数据在内存中可以以行与列的形式进行存储。为了启用In-Memory Column Store,必须将inmemory_size参数设置为非零值。

可以在以下级别启用In-Memory Column Store
.列
.表
.物化视图
.表空间
.分区

如果在表空间级别启用In-Memory Column Store,那么所有存储在该表空间中的所有表与物化视图将抽默认启用In-Memory Column Store。可以将一个数据库对象的所有列或者将一个数据库对象的部分列加载到In-Memory Column Store中。类似地,对于分区表或物化视图,可以将所有分区或部分分区加载到In-Memory Column Store中。

在In-Memory Column Store中存储数据库对象可以显然提高对数据库对象执行以下类型操作的性能:
.查询扫描大量数据并且使用=,< ,>与in操作来进行过滤
.查询从表中或者从有大量列的物化视图中选择少量列,比如从有100列的表中选择5列
.查询对小表与大表进行关联
.查询将聚集数据

通常情况下,创建多列索引可以提高分析与报告查询的性能。这些索引可能影响DML语句的性能。当数据库对象被加载到In-Memory Column Store中,用于分析或报告查询的索引可以被减少或消除而不会影响查询的性能。消除这些索引可以提高事务和数据加载操作的性能。

可以通过对以下语句增加inmemory子句的方式来对数据库对象启用In-Memory Column Store:
.create table
.alter table
.create tablespace
.alter tablespace
.create materialized view
.alter materialized view

为了判断那个数据库对象被加载到In-Memory Column Store中,可以查询v$in_segments视图:

SQL> alter table jy inmemory;
Table altered

SQL> 
SQL> SELECT OWNER, SEGMENT_NAME, INMEMORY_PRIORITY, INMEMORY_COMPRESSION   FROM V$IM_SEGMENTS;
OWNER                                                                            SEGMENT_NAME                                                                     INMEMORY_PRIORITY INMEMORY_COMPRESSION
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ----------------- --------------------

SQL> 
SQL> SELECT OWNER, SEGMENT_NAME, INMEMORY_PRIORITY, INMEMORY_COMPRESSION   FROM V$IM_SEGMENTS;
OWNER                                                                            SEGMENT_NAME                                                                     INMEMORY_PRIORITY INMEMORY_COMPRESSION
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ----------------- --------------------

SQL> select count(*) from jy;
  COUNT(*)
----------
     72736

SQL> 
SQL> SELECT OWNER, SEGMENT_NAME, INMEMORY_PRIORITY, INMEMORY_COMPRESSION  FROM V$IM_SEGMENTS;
OWNER                                                                            SEGMENT_NAME                                                                     INMEMORY_PRIORITY INMEMORY_COMPRESSION
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ----------------- --------------------
JY                                                                               JY                                                                               NONE              FOR QUERY LOW

In-Memory Column Store不能对以下操作类型提高性能:
.有复杂谓词的查询
.选择大量列的查询
.返回大量数据行的查询
.使用大表联接的查询

SYS用户的对象并且存储在system或sysaux表空间中,那么这种对象不能被加载到In-Memory Column Store中。

In-Memory Column Store压缩方法
在In-Memory Column Store中,数据是可以被压缩的,并且SQL查询可以直接对压缩数据进行查询。In-Memory Column Store压缩方法有:
no memcompress 数据不会被压缩

memcompress for dml 这种方法只对DML操作的数据进行优化与压缩

memcompress for query low 这种方法将提供最佳的查询性能。这种压缩方法比memcompress for dml所压缩的数据要多但比
memcompress for query high压缩方法所压缩的数据要少。当在create或alter语句中指定inmemory子句但没指定压缩方法时或者当指定memcompress for query而没指定low或high时所使用的缺省值

memcompress for query high 这种方法会提供杰出的查询性能。这种压缩方法所压缩的数据量要比memcompress for query low方法多,但比memcompress for capacity low方法所压缩的数据量少

memcompress for capacity low 这种方法会提供很好的查询性能。这种压缩方法所压缩的数据量要比memcompress for query high方法所压缩的数据量多,但比memcompress for capacity high方法所压缩的数据量少。当指定memcompress for capacity而没有指定low或high时所使用的缺省值

memcompress for capacity high 这种方法提供的查询性能一般,但它的所压缩的数据量最多

SQL> alter table jy inmemory;
Table altered

SQL> 
SQL> SELECT OWNER, SEGMENT_NAME, INMEMORY_PRIORITY, INMEMORY_COMPRESSION
  2  FROM V$IM_SEGMENTS;
OWNER                                                                            SEGMENT_NAME                                                                     INMEMORY_PRIORITY INMEMORY_COMPRESSION
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ----------------- --------------------

SQL> 
SQL> SELECT OWNER, SEGMENT_NAME, INMEMORY_PRIORITY, INMEMORY_COMPRESSION
  2  FROM V$IM_SEGMENTS;
OWNER                                                                            SEGMENT_NAME                                                                     INMEMORY_PRIORITY INMEMORY_COMPRESSION
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ----------------- --------------------

SQL> select count(*) from jy;
  COUNT(*)
----------
     72736

SQL> 
SQL> SELECT OWNER, SEGMENT_NAME, INMEMORY_PRIORITY, INMEMORY_COMPRESSION
  2  FROM V$IM_SEGMENTS;
OWNER                                                                            SEGMENT_NAME                                                                     INMEMORY_PRIORITY INMEMORY_COMPRESSION
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ----------------- --------------------
JY                                                                               JY                                                                               NONE              FOR QUERY LOW

关键字memcompres必须要出现在inmemory后面

SQL> alter table jy  inmemory memcompress for query high;
Table altered

SQL> SELECT OWNER, SEGMENT_NAME, INMEMORY_PRIORITY, INMEMORY_COMPRESSION FROM V$IM_SEGMENTS;
OWNER                                                                            SEGMENT_NAME                                                                     INMEMORY_PRIORITY INMEMORY_COMPRESSION
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ----------------- --------------------

SQL> select count(*) from jy;
  COUNT(*)
----------
     72736


SQL> SELECT OWNER, SEGMENT_NAME, INMEMORY_PRIORITY, INMEMORY_COMPRESSION FROM V$IM_SEGMENTS;
OWNER                                                                            SEGMENT_NAME                                                                     INMEMORY_PRIORITY INMEMORY_COMPRESSION
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ----------------- --------------------
JY                                                                               JY                                                                               NONE              FOR QUERY HIGH

In-Memory Column Store Data Population Options
当对数据库对象雇用In-Memory Column Store时,可以让数据库来控制数据库对象何时被加载到In-Memory Column Store中,或者可以指定一个优先级别来决定加载队列中数据库对象的优先级。Oracle SQL包含一个inmemory priority子句来对于加载队列提供更多的控制。例如,它可以在加载其它数据库对象的数据之前将更重要或不重要的数据库对象的数据进行加载。

在In-Memory Column Store中对于加载数据库对象提供了以下优先级:
priority none Oracle数据库控制何时将数据库对象的数据加载到In-Memory Column Store中。对这个数据库对象进行扫描会触发将这个对象加载到In-Memory Column Store中。当priority在inmemory子句中没有指定时这是它的缺省级别。例如当对表jy修改In-Memory Column Store的压缩方法后,立即查询这个对象是否被加载到In-Memory Column Store中会发现并没有加载

SQL> alter table jy  inmemory memcompress for query low;
Table altered

SQL> SELECT OWNER, SEGMENT_NAME, INMEMORY_PRIORITY, INMEMORY_COMPRESSION FROM V$IM_SEGMENTS;
OWNER                                                                            SEGMENT_NAME                                                                     INMEMORY_PRIORITY INMEMORY_COMPRESSION
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ----------------- --------------------

因为没有指定加载数据库对象的优先级,所以需要执行查询进行扫描来触发将其加载到In-Memory Column Store中

SQL> select count(*) from jy;
  COUNT(*)
----------
     72736

SQL> SELECT OWNER, SEGMENT_NAME, INMEMORY_PRIORITY, INMEMORY_COMPRESSION FROM V$IM_SEGMENTS;
OWNER                                                                            SEGMENT_NAME                                                                     INMEMORY_PRIORITY INMEMORY_COMPRESSION
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ----------------- --------------------
JY                                                                               JY                                                                               NONE              FOR QUERY LOW

现在可以看到表jy已经被加载到In-Memory Column Store中了,可以看到inmemory_priority为none

priority low 在优先级为none的数据库对象之前将优先级为low的对象加载到In-Memory Column Store中,但在优先级为medium,high或critical的数据库对象被加载到In-Memory Column Store中之后才会将优先级为low的对象加载到In-Memory Column Store中

priority medium 在优先级为none或low的数据库对象之前将优先级为medium的对象加载到In-Memory Column Store中,但在优先级为high或critical的数据库对象被加载到In-Memory Column Store中之后才会将优先级为medium的对象加载到In-Memory ColumnStore中。

priority high 在优先级为none,low或medium的数据库对象之前将优先级为high的对象加载到In-Memory Column Store中,但在优先级为critical的数据库对象被加载到In-Memory Column Store中之后才会将优先级为medium的对象加载到In-Memory ColumnStore中。

priority critical 在优先级为none,low,medium或high的数据库对象之前将优先级为critical的对象加载到In-Memory Column Store中

当多个数据库对象设置了优先级而不为none时,Oracle数据库将对象的所有数据基于它们的优先级别进行排队来将它们加载到In-Memory Column Store中。优先为critical的对象将会最先被加载,接下来就是优先级为high的对象会被加载,依此类推。如果在In-Memory Column Store中没有足够的空间,那么额外要被加载的对象直到有足够空间之前是不会被加载的。当数据库被重启时,优先级不为none的对象会在数据库启动时被加载到In-Memory Column Store中。对于优先级不为none的数据库对象来说,alter table或alter materialized view DDL语句在DDL语句被记录在In-Memory Column Store之前不会返回执行结果。

下面的例子,创建表t1,在没有设置inmemory属性时,表t1是没有被加载到In-Memory Column Store中的

SQL> create table t1 as select * from jy;
Table created

SQL> select owner, segment_name, inmemory_priority, inmemory_compression from v$im_segments;
OWNER                                                                            SEGMENT_NAME                                                                     INMEMORY_PRIORITY INMEMORY_COMPRESSION
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ----------------- --------------------
JY                                                                               JY                                                                               NONE              FOR QUERY LOW

修改表的inmemory属性,并且设置了加载优先级为high,数据压缩为memcompress for query high,在修改完成后,表t1就已经被加载到In-Memory Column Store中了

SQL> alter table t1 inmemory priority high memcompress for query high;
Table altered


SQL> select owner, segment_name, inmemory_priority, inmemory_compression from v$im_segments;
OWNER                                                                            SEGMENT_NAME                                                                     INMEMORY_PRIORITY INMEMORY_COMPRESSION
-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ----------------- --------------------
JY                                                                               JY                                                                               NONE              FOR QUERY LOW
JY                                                                               T1                                                                               HIGH              FOR QUERY HIGH

优先级的设置必须应用给整个表或一个表分区。对表中的不同列集合指定不同的In-Memory Column Store加载优先级别是不允许的。如果一个段在磁盘上的大小为64KB或者更小,那么这个对象不会被加载到In-Memory Column Store中。因此对于一些较小的对象虽然启用了In-Memory Column Store,但可能并不会被加载到In-Memory Column Store中。

In-Memory Column Store相关的初始化参数
inmemory_size:这个参数设置实例中的In-Memory Column Store的大小,它的缺省值为0,这意味着没有雇用In-Memory Column Store。为了雇用In-Memory Column Store这个参数必须设置为非0值。如果这个参数设置为非0值,那么它的最小值为100M。

在多租户环境中,在root容器中设置这个参数是对整个CDB生效的。这个参数也可以对每个PDB进行设置来限制每个PDB中的In-Memory Column Store的大小。PDB的In-Memory Column Store的总大小可以小于,等于或大于CDB的值。然而CDB所设置的In-Memory Column Store的大小是整个CDB中,包含root与所有PDB的In-Memory Column Store可以使用的最大内存,这意味着PDB可以CDB中所有可以使用的In-Memory Column Store的内存。

inmemory_force:这个参数可以对表与物化视图启用In-Memory Column Store或者对表与物化视图禁用In-Memory Column Store,设置这个参数为default,这是它的缺省值,它将允许通过单个对象的inmemory或no inmemory属性来决定是否将对象加载到In-Memory Column Store中。将这个参数设置为off来指定所有表与物化视图将禁用In-Memory Column Store.

inmemory_clause_default:这个参数能让你对新表与物化视图指定一个缺省的In-Memory Column Store子句。不设置这个参数或者设置为一个空字符串来指定对于新表与物化视图不存在缺省的In-Memory Column Store子句。将这个参数设置为no inmemory与它的缺省值(空字符串)有相同的作用。将这个参数设置为有效的inmemory子句来指定它为所有新表与物化视图的In-Memory Column Store的缺省值。这个子句可以包含In-Memory Column Store压缩方法与数据加载选项。如果子句以inmemory开头,那么所有新表与物化视图,包含那些没有inmemory子句的对象,将会被加载到In-Memory Column Store中。如果子句忽略了inmemory,那么它只会对在创建时指定了inmemory子句的新表与物化视图雇用In-Memory Column Store。

inmemory_query:这个参数指定是否允许in-memory查询。将这个参数设置为enable,它是缺省值,允许查询所访问的对象被加载到In-Memory Column Store中,将这个参数设置为disable来禁止将查询所访问的对象加载到In-Memory Column Store中。

inmemory_max_populate_servers:这个参数指定执行In-Memory Column Store加载操作的后台加载服务器进程的最大数量,因此这些服务器进程不能超过系统所能承受的负载。基于系统中的CPU内核数来设置一个合理值。

inmemory_trickle_repopulate_servers_percent:这个参数用来限制用于In-Memory Column Store重新加载的后台加载服务器进程的最大数量,trickle repopulation被设计只占加载服务器进程总数量的很小百分比。这个参数的值为
inmemory_max_populate_servers参数值的一个百分比。例如,如果这个参数设置为10,并且inmemory_max_populate_servers被设置为10,那么将会有一个cpu内核用于tricle repopulation操作。

optimizer_inmemory_aware:这个参数用来启用或禁用优化器成本模型来增强In-Memory Column Store。将这个参数设置为false,将造成优化器在优化SQL语句时忽略有in-memory属性的表。

对数据库启用In-Memory Column Store
在表,表空间或物化视图被启用In-Memory Column Store之前,必须对数据库启用In-Memory Column Store。对数据库启用In-Memory Column Store需要执行以下步骤:
1.确保数据库的兼容性参数设置为12.1.0或更高版本。
2.将inmemory_size参数设置为非零值。当对spfile参数文件设置这个参数时,可以使用alter system语句,并且必须指定scope=spfile,它的最小值为100M。
3.重启数据库,为了在SGA中初始化In-Memory Column Store必须要重启数据库。
4.可选操作,可以执行下面的语句来检查当前为In-Memory Column Store所分配的内存大小。

SQL> show parameter inmemory_size

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
inmemory_size                        big integer 1G

对表启用与禁用In-Memory Column Store
通过对create table或alter table语句指定inmemory子句来对表启用In-Memory Column Store。通过对create table或alter table语句指定no inmemory子句对表禁用In-Memory Column Store。

为了对表启用与禁用In-Memory Column Store,需要执行以下步骤:
1.确保数据库已经启用了In-Memory Column Store
2.使用有合适权限的用户连接到数据库并创建或修改表
3.执行create table或alter table并指定inmemory或no inmmeory子句

创建表并启用In-Memory Column Store
下面的例子将创建一个名叫test_inmem的表并启用In-Memory Column Store

SQL> create table test_inmem(id number(5) primary key,test_col varchar2(15)) inmemory;

Table created.

这个例子对inmemory子句使用缺省值。因此将使用memcompress for query与priority none

对表启用In-Memory Column Store
下面的例子将对表oe.product_information表启用In-Memory Column Store:

SQL> alter table oe.product_information inmemory;

Table altered.

这个例子对inmemory子句使用缺省值。因此将使用memcompress for query与priority none

对表启用In-Memory Column Store并且指定压缩方法为for capacity low
下面的例子对表oe.product_information启用In-Memory Column Store并且指定压缩方法为for capacity low:

SQL> alter table oe.product_information inmemory memcompress for capacity low;

Table altered.

这个例子对priority子句使用缺省值,因此优先级为priority none。

对表启用In-Memory Column Store并且指定数据加载优先级为high
下面的例子对表oe.product_information启用In-Memory Column Store并且指定数据加载优先级为priority high

SQL> alter table oe.product_information inmemory priority high;

Table altered.

这个例子对memcompress子句使用缺省值,因此压缩方法为memcompress for query

对表启用In-Memory Column Store并且指定压缩方法为for capacity high,数据加载优先级为low
下面的例子对表oe.product_information启用In-Memory Column Store并且指定压缩方法为for capacity high,数据加载优先级为priority low

SQL> alter table oe.product_information inmemory  memcompress for capacity high priority low;

Table altered.

对表中的列启用In-Memory Column Store
下面的例子对表oe.product_information表中的一些列启用In-Memory Column Store而剩余的列不雇用。并且对不同的列子集启用不同的In-Memory Column Store压缩方法。

SQL> alter table oe.product_information
  2  inmemory memcompress for query (product_id, product_name, category_id, supplier_id, min_price)
  3  inmemory memcompress for capacity high (product_description, warranty_period, product_status, list_price)
  4  no inmemory (weight_class, catalog_url);

Table altered.

在这个例子中定义如下
以product_id开始至min_price结束的字段列表启用了In-Memory Column Store并且指定压缩方法为memcompress for query.

以product_description开始至list_price结束的字段列表启用了In-Memory Column Store并且指定压缩方法为memcompress for
capacity high

字段weight_class与catalog_url没有启用In-Memory Column Store。

在这个例子中对于priority子句使用了缺省值。因此将使用priority none。可以查询v$in_column_level视图,注意优先级的设置
必须应用给整个表或表分区。对不同字段列表指定不同的优先级是不允许的。

对表禁用In-Memory Column Store
为了对表禁用In-Memory Column Store,只需使用no inmemory子句。下面将对表oe.product_information禁用In-Memory Column Store:

SQL> alter table oe.product_information no inmemory;

Table altered.

对表空间启用与禁用In-Memory Column Store
在使用create tablespace语句来创建表空间时可以通过指定inmemory子句来让表空间启用In-Memory Column Store。也可以使用包含inmemory子句的alter tablespace语句来修改表空间让其启用In-Memory Column Store。

通过执行包含no inmemory子句的create tablespace或alter tablespace语句来对表空间禁用In-Memory Column Store。

当表空间启用In-Memory Column Store之后,存储在该表空间中的所有表与物化视图会默认启用In-Memory Column Store。对于表,物化视图与表空间inmemory子句是相同的。当对表空间启用In-Memory Column Store时在inmemory子句之前,并且当对表空间禁用In-Memory Column Store时在no inmemory子句之前需要使用default存储子句。

当表空间启用In-Memory Column Store后,存储在表空间中的单个表与物化视图可以有不同的in-memory设置,并且单个对象的设置会覆盖表空间级别的设置。例如,如果表空间对于数据加载优先级设置为priority low,但在表空间中的表的数据加载优先级设置为priority high,那么表将使用priority high。

为了对表空间启用或禁用In-Memory Column Store,执行以下操作:
1.确保对数据库启用了In-Memory Column Store。
2.使用有合适权限的用户连接到数据库来创建或修改表空间。
3.执行有inmemory或no inmemory子句的create tablespace或alter tablespace语句

下面的例子将创建一个表空间tbs1并对它启用In-Memory Column Store

SQL> create tablespace tbs1 datafile 'tbs1.dbf' size 40m online default inmemory;

Tablespace created.

这个例子对inmemory子句使用了缺省值,因此压缩方法为memcompress for query,数据加载优先级为priority none。

下面的例子将修改表空间tbs1让其启用In-Memory Column Store并且指定数据压缩方法为memcompress for capacity high,数据加载优先级为priority low

SQL> alter tablespace tbs1 default inmemory memcompress for capacity high priority low;

Tablespace altered.

对物化视图启用与禁用In-Memory Column Store
通过执行包含inmemory子句的create materialized view或alter materialized view语句来对物化视图启用In-Memory Column Store。通过执行包含no inmemory子句的ceate materialized view或alter materialized view语句来对物化视图禁用In-Memory Column Store。

为了对物化视图雇用与禁用In-Memory Column Store,需要执行以下操作:
1.确保对数据库启用了In-Memory Column Store。
2.使用有合适权限的用户连接到数据库来创建或修改物化视图。
3.执行包含inmemory或no inmemory子句的create materialized view或alter materialized语句

下面的例子创建一个名叫oe.prod_info_mv的物化视图并且启用In-Memory Column Store:

SQL> create materialized view oe.prod_info_mv inmemory as select * from oe.product_information;

Materialized view created.

这个例子对inmemory子句使用缺省值。因此将使用memcompress for query与priority none

下面的例子将让物化视图oe.prod_info_mv启用In-Memory Column Store并且指定数据加载优先级为priority high

SQL> alter materialized view oe.prod_info_mv inmemory priority high;

Materialized view altered.

Data Pump与In-Memory Column Store
在impdp命令中通过使用transform=inmemory:y选项来导入启用了In-Memory Column Store的数据库对象。使用这个选项,Data Pump将对所有对象保留In-Memory Column Store子句。当transform=inmemory:n时,Data Pump将对对象删除In-Memory Column Store子句。

也可以使用transform=inmemory_clause:string选项来覆盖在dump文件中数据库对象所设置的In-Memory Column Store子句。例如可以使用这个选项来修改被导入对象的In-Memory Column Store的数据压缩方法。

Proudly powered by WordPress | Indrajeet by Sus Hill.