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这是极端粗鲁的。这就像你正在读一本书时,有一个人说”让我看看”,并从你手中抢走一样。许多进程可以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的数量,增加这个参数的大小将会显著地影响性能。

发表评论

电子邮件地址不会被公开。