Oracle Latch的学习【原创】

Latch详解

                                                                                                                                                   — MaxChou

本文以学习为目的,大部分内容来自网络转载。

什么是Latch

串行化

    数据库系统本身是一个多用户并发处理系统,在同一个时间点上,可能会有多个用户同时操作数据库。多个用户同时在相同的物理位置上写数据时,不能发生互相覆盖的情况,这叫做串行化。串行化会降低系统的并发性,但这对于保护数据结构不被破坏来说则是必需的。在Oracle数据库中,通过闩锁(latch)、锁定(lock)、互斥(mutex来实行串行化,保护数据结构一致性的。

 

Latch的定义和作用

    Oracle数据库使用闩锁(latch)来管理SGA内存的分配和释放,Latch是用于保护SGA中共享数据结构的一种串行化锁定机制。Latch的实现是与操作系统相关的,尤其和一个进程是否需要等待一个latch、需要等待多长时间有关。
    Latch是一种能够极快地被获取和释放的锁,它通常用于保护描述buffer cacheblock的数据结构。 

比如数据缓存中的某个块要被读取,我们会获得这个块的latch,这个过程叫做pin;另外一个进程恰好要修改这个块,他也要pin这个块,此时他必须等待。当前一个进程释放latch后才能pin住,然后修改。如果多个进程同时请求的话,他们之间将出现竞争,没有一个入队机制,一旦前面进程释放latch,后面的进程就蜂拥而上,没有先来后到的概念,这个和Lock是有本质区别的。这一切都发生的非常快,因为Latch的特点是快而短暂,当然这个只是大致过程。

 

如何获取Latch

任何时候只有一个进程可以访问内存中的某一个块,如果进程因为别的进程正占用块而无法获得Latch时,他会对CPU进行一次spin(旋转),时间非常的短暂,spin过后继续获取,不成功仍然spin,直到spin次数到达阀值限制(这个由隐含参数_spin_count指定),此时进程会停止spin,进行短期的休眠,休眠过后会继续刚才的动作,直到获取块上的Latch为止。进程休眠的时间也是存在算法的,他会随着spin次数而递增,以厘秒为单位,如11224488...休眠的阀值限制由隐含参数_max_exponential_sleep控制,默认是2秒,如果当前进程已经占用了别的Latch,则他的休眠时间不会太长(过长会引起别的进程的Latch等待),此时的休眠最大时间有隐含参数_max_sleep_holding_latch决定,默认是4厘秒,这种时间限制的休眠又称为短期等待。

另外一种情况是长期等待锁存器Latch Wait Posting),此时等待进程请求Latch不成功,进入休眠,他会向锁存器等待链表(Latch Wait List)压入一条信号,表示获取Latch的请求,当占用进程释放Latch时会检查Latch Wait List,向请求的进程传递一个信号,激活休眠的进程。Latch Wait List是在SGA区维护的一个进程列表,他也需要Latch来保证其正常运行,默认情况下share pool latchlibrary cache latch是采用这个机制,如果将隐含参数_latch_wait_posting设置为2,则所有Latch都采用这种等待方式,使用这种方式能够比较精确的唤醒某个等待的进程,但维护Latch Wait List需要系统资源,并且对Latch Wait ListLatch的竞争也可能出现瓶颈。

如果一个进程请求、旋转、休眠Latch用了很长时间,他会通知PMON进程,查看Latch的占用进程是否已经意外终止或死亡,如果是,则PMON会清除释放占用的Latch资源。

现在大家可以明白,对Latch获取的流程了,请求-SPIN-休眠-请求-SPIN-休眠...占用。这里有人会问为什么要SPIN,为什么不直接休眠等待?这里要明白休眠意味着什么,他意味着暂时的放弃CPU,进行上下文切换(context switch),这样CPU要保存当前进程运行时的一些状态信息,比如堆栈、信号量等数据结构,然后引入后续进程的状态信息,处理完后再切换回原来的进程状态,这个过程如果频繁的发生在一个高事务,高并发进程的处理系统里面,将是个很昂贵的资源消耗,所以他选择了spin,让进程继续占有CPU运行一些空指令,之后继续请求,继续spin,直到达到_spin_count值,这时会放弃CPU,进行短暂的休眠,再继续刚才的动作。

 

当尝试获得Latch的时候,可能会消耗大量的CPU时间,系统看上去很忙,但是并没有做多少实际工作。

系统发生关于Latch的等待是没法避免的,因为这是Oracle的运作机制,当你看到很高的Latch get时并不意味着你的系统需要调整,有时候很高的get值背后只有很短的等待时间,我们调整的对象应该以消耗的时间来圈定,而不是只看到一个很高的获取次数值。当然,获取值异常的高出别的等待时间几十万倍时我们还是要关心的,Oracle关于Latch的等待非常繁多,主要的包括share poollibrary cachecache buffer chainsbuffer busy wait

Oracle使用两种数据结构来进行shared pool的并发控制:lockpinLockpin具有更高的级别,Lockhandle上获得,在pin一个对象之前,必须首先获得该handlelock
    Lock主要有三种模式NullshareExclusive。在读取访问对象时,通常需要获取Null()模式以及share(共享)模式的锁定;在修改对象时,需要获得Exclusive(排他)锁定。

在锁定了Library Cache对象以后,一个进程在访问之前必须pin该对象。同样pin有三种模式Nullsharedexclusive。只读模式时获得共享pin,修改模式获得排他pin

通常我们访问、执行过程、Package时获得的都是共享pin,如果排他pin被持有,那么数据库此时就要产生等待。

 

PS 

    latch是用于保护SGA区中共享数据结构的一种串行化锁定机制。它不仅仅用于buffer cache,还用于shared pool以及log buffer等。

    闩锁和锁定既有相同点又有不同点。相同点在于它们都是用于实现串行化的资源。而不同点则在于闩锁(Latch)是一个低级别、轻量级的锁,获得和释放的速度很快,以类似于信号灯的方式实现。而锁定(Lock)则可能持续的时间很长,通过使用队列,按照先进先出的方式实现。也可以简单地理解为闩锁是微观领域的,而锁定则是宏观领域的。

 

Latch的类型:

Willing-to-wait类型Latch

  如果一个进程在第一次尝试中没有获得该Latch,那么它会等待并再次尝试一次,如果

  经过_spin_count次争夺不能获得Latch,该进程就会转入睡眠状态,睡眠结束后,按

  顺序重复之前的步骤,并且睡眠时间会越来越长。

Immediate类型Latch

  如果该Latch不能立即得到,该进程不会等待而是继续执行其他操作。

 

 

术语解惑

-转自Askmaclean的回答

首先理解cache buffer chain(hash chain) cache buffer bucket (hash bucket)

对于Buffer cache管理而言oracle所需要解决的问题包括几个:

  • 如何快速定位一个data buffer header

因为Buffer cache中的data buffer header是非常多的,若为了找一个data buffer header

而去对所有的buffer header都扫描一遍 ,那将是非常低效的。举个例子来说,服务进

程要有读取datafile 5 block 10的需求,这个时候难道服务进程一开始就知道data file 5

block 10在是不是在Buffer cache中,在Buffer cache中的哪里?这些信息Server

process都是不知道的。如果data buffer header被使用普通的双向链表组织,那么如果

要确定一个data buffer 是否在Buffer Cache中,那么需要把这个双向链表上所有的

buffer header都查看一遍,这是十分低效的。

  • data buffer header高效的并发管理,避免出现争用:

为了实现高效管理data buffer header的目的,oracle使用hash buckets的结构来组织data  

buffer header,通过对data buffer header的不同rdbaclassHASH算法来实现对buffer

header的高效管理,通俗来说HASH做的就是一件事,例如data file 4上的block 18

block 19是应用经常要访问的热快,经过HASH算法之后这2个块就不会落在同一个

HASH Buckets中,这样避免了对同一条hash chain的争用。    

oracle又通过hash chains( cache buffer chain)将一个bucket上的buffer header串起来,

注意:同一个data blockoracle中可能会有多个buffer,分别对应为一个current block

和可能的多个cr block,这些block都同一条cache buffer chains上。

为了实现对cache buffer chains的并发控制需要用到latch来管理,所以会有cache buffer

chains latch

 

    CBC latch管理是Buffer cache Internal的主要部分,值得研究 但是并非一段文字所能全部描述。

你也可以参考下面这个图示:

 

 

BUFFER CACHE相关的Latch

BUFFER CACHE相关的Latch

Latchcache buffers lru chain

Latchcache buffers chains

    既然Latch是用来保护共享内存块不被并发性破坏,那就需要了解Buffer Cache的原理,进而得知需要用到Latch的情况,才能有应对的方案。

    Buffer Cache是用来缓存数据块的地方,那么数据的查询和修改都要通过它来完成,接下来看访问数据的流程:

当一个进程想要访问数据时,首先要查找Buffer Cache中是否已经存在:

(Y)->如果数据在Buffer Cache中存在,则根据数据的状态来判断是否可以直接访问,还是需要构造一致性读块(CR块);

(N)->如果数据在Buffer Cache中不存在,则需要从磁盘中读取数据块到Buffer Cache去,这个时候需要在Buffer Cache中寻找足够的内存空间来读取相关数据块。

 

如何确定Buffer Cache中是否已经存在需要的数据块

    Y->如果数据块在Buffer Cache中存在的情况

  • 根据要查找的数据块的DBA(Data Block Address)等信息,通过Hash算法(Hash Bucket=MOD(Data Block Address,_DB_BLOCK_HASH_BUCKETS)),得到该数据块所在的Hash Bucket
  • 定位到对应的Hash Bucket上,在该Hash Bucket对应的Cache Buffers Chain中加上Cache Buffers Chains Latch,然后从Cache Buffers Chain对于的第一个Data Buffer Header开始扫描查找,直至最后一个。在这个扫描查找过程中,为了防止对Cache Buffers Chain产生并发访问,将一直持有Cache Buffers Chains Latch

Cache Buffers Chain上查找的具体逻辑如下,根据我手画的图理解:

 

1.比较Data Buffer Header上所记录的Block地址,不符合条件的就跳过此Data Buffer Header

2.跳过statusCRData Buffer Header

3.如果Data Buffer Header状态为reading则等待,直到状态改变后比较Buffer Header记录的Block地址是否符合;

4.若发现Block地址符合的Data Buffer Header,查该Data Buffer Header是否位于正在使用的list上,如果是,则判断已存在的lock mode,与要求的lock mode是否兼容,如果兼容则返回该Data Buffer Header中记录的Buffer地址,将当前process id放入Data Buffer Header所处的正在使用的list上;

5.如果lock mode不兼容,用Data Buffer Header所指向的Buffer中的内容构建一个xcurrentBuffer和一个CR状态的Data Buffer Header(指向新建立xcurrent状态的复制Buffer)。

6.搜索完整个Hash Chain还未发现需要的Data Buffer Header,从disk读取数据块,读入到Buffer Cache中,相应的Data Buffer Header挂在Cache Buffers Chain上。

Ps

Buffer Header指向的Buffer6种状态:(可以参照V$BHstatusv$bh:非常详细地记录了数据块在数据缓冲区内的使用情况,一条记录对应一个block的详细记录。

v$bh来自于基表x$bhx$le

1. Free:可以被重用的Block

2. xcurrent:以exclusive方式获取的当前模式的Blockinsertupdatedelete时产生),

  Scurrent: 可以与其他instance共享的当前模式Block

3. CR:一致读块,永远不会写入disk

4. reading:正从disk读取出来的块;

5. mreciver:正在进行介质恢复的Block

6. ircovery:正在进行instance recoveryBlock

  • 获得第二步中查到的可以直接访问的Data Buffer Header或者构造一致性读后的Data Buffer Header中的Buffer地址,到Buffer Memory查找对应数据块。

 

N->如果数据块在Buffer Cache中不存在(在Hash Chain中查找不到对应Data Buffer   

Header

  • 需要从数据文件(磁盘)中读取数据块到Buffer Cache中去。这时候,需要在Buffer Cache中寻找足够的内存空间来存放相关的数据块。

 

 

如何才能快速查询到数据块

Buffer Cache是很大的,如果一个Buffer一个Buffer的扫描是相当耗费资源和查询时间的,所以要通过类似目录(Hash Bucket)的方法,让数据库能快速定位数据块的位置。

下面是关于Buffer Cache的示意图

 

先看以下几点:

  • 图中右边有一块Buffers Memory,其中每一小格代表一个Buffers(用来存放数据文件中读取的数据块Block);
  • 图中左边有许多Data Buffer Header用虚线指向Buffers Memory中相应的Buffer
  • 图中左边有许多实线箭头,这些箭头(其实就是数据结构的链表结构中的指针)将不同的Data Buffer Header连接成一条Hash Chain,也就是Cache Buffers Chain(双向链表);
  • Hash Bucket,其实这只是一个逻辑概念,即每一个Hash Bucket都会有一条Hash Chain来将Data Buffer Header(按照Hash算法分类后)连接起来,并由一个Cache Buffers Chains Latch来进行管理其并发操作;
  • 每当将一个Block读入到Buffer Cache的时候,首先会构造一个与之相对应的Data Buffer Header,然后根据Hash算法(Hash Bucket=MOD(Data Block Address,_DB_BLOCK_HASH_BUCKETS)),将Data Buffer Header放到对应的Hash BucketCache Buffers Chain中去,并在Data Buffer Header中存放如下信息:

1.存放该BlockBuffer Cache中实际存储地址;  

2.存放该Block的类型(data,segment header,undo header,undo Block等类型);

3.由于此Data Buffer Header所在的Cache Buffers Chain(Hash Chain),是通过在Data Buffer Header保存指向前一个Data Buffer Header的指针和指向后一个Data Buffer Header的指针方式实现,所以还存指针;

4.存储lru,lruw,ckptq,fileq等队列,一样是通过记录前后Data Buffer Header指针方式实现;

5.当前该Data Buffer Header所对应的数据块的状态以及标记;

6.Data Buffer Header被访问的次数(touch次数)

7.正在等待该Data Buffer Header的进程列表(waiter list)及正在使用此Data Buffer Header(user list)

  • Hash Latch,即Cache Buffers Chain LatchBuffer CacheHash Bucket的个数由隐含参数_db_block_hash_buckets决定,Cache Buffers Chains Latch的个数由隐含参数_db_block_hash_latches决定。

    

    在上面的Buffer Cache示意图中可以看到两条链(LRULRUW),这两条链分别将Data Buffer Header连接起来,和Cache Buffers Chain类似:

LRULRUW的作用:

  • LRU表示Least Recently Used,也就是指最少最近使用的Data Buffer Header链表,LRU链表串联起来的Data Buffer Header都指向可用数据块(Free Buffer);
  • LRUW则表示Least Recently Used Write,也叫做Dirty List,即脏数据块链表,LRUW串联起来的都是修改过但是还没有写入数据文件的数据块所对应的Data Buffer HeaderDirty Buffer;
  • 一个Data Buffer Header 要么在LRU上,要么在LRUW上,不能同时存在于两个链表上。

     所以当查找数据块在Buffer Cache中不存在的时候(即Hash Chain中查找不到对应的

     Data Buffer Header的情况下):就要扫描LRU List寻找FreeBuffer,在扫描过程将

     持有Cache Buffers Lru Chain Latch(其Latch数量由隐含参数_db_block_lru_latches

     定),扫描过程中会把已经修改过的Buffer移动到LRUW链表上;

  • 找到足够的Buffer之后,将数据块读入到Buffer Cache,构造一个与之对应的Data Buffer Header,然后根据Hash算法(Hash Bucket=MODData Block Address_DB_BLOCK_HASH_BUCKETS)),将Data Buffer Header放到对应的Hash BucketCache Buffers Chain中去,并在Data Buffer Header中存放相关的信息。

 

 

 

 

 

 

 

 

 

 

 

 

Buffer Cache中查找数据块的总流程:

 

 

可能导致Latch争用的两种情况

A. 某一进程过长时间的持有Latch,导致其他进程不能正常的得到Latch,只能等待;

B. 可能存在的大量的Latch请求。

 

4.1 如果出现Cache Buffers Chains Latch严重争用,根据以上原理,那么可能有如下原因:

  • 当多个会话重复访问一个或多个由同一个子Cache Buffers Chains Latch保护的块是热点块(可以关注X$BH中的TCH字段);
  • 大量并发执行的低效SQL,低效的SQL通常需要获取大量的逻辑读,而得到一次逻辑IO就获得一次Cache Buffers Chains Latch
  • Hash Bucket中存在长的Cache Buffers Chains,导致查询数据块时候,长时间持有Latch

 

4.2 如果出现Cache Buffers Lru Chain Latch严重争用,那么可能有如下原因:

  • 可能Buffer Cache分配的空间不够,导致读数据到Buffer Cache的时候,不断的扫描LRU List

 

  1. 测试:模拟Cache Buffers Chains Latch争用

 

5.1 创建表test,总共一条记录,共1Block

SQL> select * from test;

 

        ID

----------

         1

 

SQL> select dbms_rowid.ROWID_RELATIVE_FNO(rowid) file#,dbms_rowid.ROWID_BLOCK_NUMBER(rowid) block#

    from test;

 

     FILE#     BLOCK#

---------- ----------

         1      35337

 

5.2 创建存储过程用于模拟一个Block不断的查询:

SQL> create or replace procedure sp_test_cbc_latch is

  2  i number;

  3  begin

  4    loop

  5      select id into i from test;

  6    end loop;

  7  end;

  8  /

 

Procedure created.

 

开始执行存储过程之前的笔记本和虚拟机CPU使用情况:

 

 

 

5.3 session 482上执行该存储过程:

SQL>  select * from v$mystat where rownum=1;

 

       SID STATISTIC#      VALUE

---------- ---------- ----------

       482          0          0

SQL> exec sp_test_cbc_latch;

 

 

5.4 在另一个会话489上查询482会话的event

SQL>  select * from v$mystat where rownum=1;

 

       SID STATISTIC#      VALUE

---------- ---------- ----------

       489          0          0

select sid,event,P1,P1RAW,P1TEXT,P2,P2TEXT from v$session where sid in (482,489);

SID    EVENT                       P1 P1RAW            P1TEXT                         P2 P2TEXT

----------------------------- ---------- ---------------- --------------- ---------- ----------------------------------------------------------------------------------

482    SQL*Net message from client    1650815232           0000000062657100 driver id       1 #bytes

489    SQL*Net message from client    1650815232           0000000062657100 driver id       1 #bytes

在执行期间可以看到Cache Buffers Lru Chain Latch都是GET成功的,不存在竞争:

SQL> select gets,misses,sleeps,spin_gets,wait_time

  2  from v$latch

  3  where name='cache buffers chains';

 

      GETS     MISSES     SLEEPS  SPIN_GETS  WAIT_TIME

---------- ---------- ---------- ---------- ----------

 369055278   10736406       1399   10735373    1113539

 

5.5 再开一个会话27执行上面的存储过程:

SQL>  select * from v$mystat where rownum=1;

 

       SID STATISTIC#      VALUE

---------- ---------- ----------

        27          0          0

SQL> exec sp_test_cbc_latch;

 

 

 

 

SQL> select sid,event,P1,P1RAW,P1TEXT,P2,P2TEXT from v$session where sid in (482,489,27);

 

       SID EVENT                                  P1 P1RAW            P1TEXT                  P2 P2TEXT

---------- ------------------------------ ---------- ---------------- --------------- ---------- ---------------

        27 cursor: pin S                  3777061920 00000000E1216420 idn                      2 value

       482 cursor: pin S                  3777061920 00000000E1216420 idn             1.1596E+11 value

       489 SQL*Net message to client      1650815232 0000000062657100 driver id                1 #bytes

 

 select gets,misses,sleeps,spin_gets,wait_time from v$latch where name='cache buffers chains'

 

      GETS     MISSES     SLEEPS  SPIN_GETS  WAIT_TIME

---------- ---------- ---------- ---------- ----------

 647782348   28420853       2277   28419082    1586813

  • 可以看到出新latchcache buffers chains等待事件,这就是两个会话同时要访问同一个Block,这个时候在一个会话持有Latch的时候,另一个会话必须spin等待获得Latch
  • 同时也能看到latchlibrary cache等待事件,这是由于在共享池进行软解玺的时候需要互动额library cache latch来扫描library cache中相应BucketChain来获得执行计划来执行SQL,因为并发性高,导致library cache latch的争用;
  • 我做实验的时候并没有出现而是出现下面的等待事件,也会看到latchshare pool或者cursorpin S等待事件:

SQL> select sid,event,P1,P1RAW,P1TEXT,P2,P2TEXT from v$session where sid in (482,489,27);

'

       SID EVENT                                  P1 P1RAW            P1TEXT                  P2 P2TEXT

---------- ------------------------------ ---------- ---------------- --------------- ---------- ---------------

        27 latch: cache buffers chains    2592449584 000000009A85A430 address                155 number

       482 cursor: pin S                  3777061920 00000000E1216420 idn             1.1596E+11 value

       489 SQL*Net message to client      1650815232 0000000062657100 driver id                1 #bytes

 

SQL> select SID,EVENT,P1,P1RAW,P2,P2TEXT from v$session where sid in (489,482,27);

       SID EVENT                                  P1 P1RAW                    P2 P2TEXT

---------- ------------------------------ ---------- ---------------- ---------- ---------------

        27 SQL*Net message from client    1650815232 0000000062657100          1 #bytes

       482 latch: shared pool             1611695912 0000000060108728        307 number

       489 SQL*Net message to client      1650815232 0000000062657100          1 #bytes

SQL> /

       SID EVENT                                  P1 P1RAW                    P2 P2TEXT

---------- ------------------------------ ---------- ---------------- ---------- ---------------

        27 cursor: pin S                  3777061920 00000000E1216420 2.0702E+12 value

       482 cursor: pin S                  3777061920 00000000E1216420 1.1596E+11 value

       489 SQL*Net message to client      1650815232 0000000062657100          1 #bytes

  • 可以看到cache buffers lru latch都是get出现严重争用,出新大量missessleepsspin_gets

SQL> select GETS,MISSES,SLEEPS,SPIN_GETS,WAIT_TIME from v$latch where name='cache buffers chains';

 

      GETS     MISSES     SLEEPS  SPIN_GETS  WAIT_TIME

---------- ---------- ---------- ---------- ----------

2145962921  114891477       6075  114886552    3355656

 

所以当存在大量latch争用的时候会大量消耗系统CPU资源。

 

 

Shared Pool相关的Latch

  1. Shared Pool相关的主要Latch

Latchshared pool

Latchlibrary cache

Oracle通过Shared Pool来实现SQL共享,减少硬解析等,而SQL的相关信息,如SQL语句文本、执行计划都存放在Shared Poollibrary cache部分。

 

  1. Library Cache的结构如下图:

 

可以看到其结构和Buffer Cache类似,为了能够在Library Cache中快速查找到对应的SQL,也是将不同的SQL语句通过Hash函数Hash后放置到对应的Hash Bucket来保存。

Object Handle

  • 这个块就是所谓的Library Cache Object Handle,这个Handle描述Library Cache中对象的一些属性,如名称(Name),所属的命名空间(Namespace)、标记(Flags)、指向对象所处的内存地址的指针(Heap0)等,对应的SQL来说,这个可以算是父游标;
  • Heap 0用来存放与对象有直接关系的一些信息,比如对象类型、对象相关的表、实际的执行计划等;
  • 同一个Hash Bucket中的Object Handle相互链接形成一条Chain/

盖国强老师的博客上也有一张简洁的图:

 

  1. 通过了解SQL执行过程来了解Shared Pool的相关Lathch
  • 当客户端执行一条SQL,这时候Oracle首先将SQL文本转换成ASCII值,然后根据Hash函数计算该SQL对应的Hash Value
  • 根据得到的Hash ValueLibrary Cache中查找对应的Bucket,然后查找Bucket里是否存在该SQL

(Y)->如果存在,则接下来查找对应的子游标,这个时候将一直持有Library Cache Latch,直到找到对应的执行计划,然后释放Latch(软解析);

(N)->如果不存在,就要去Shared Pool里面获得可用空间,来生成对应的Library Cache对象,这个时候就要获得Shared Pool LatchShared PoolFree listShared Pool通过Free List管理Free Chunk)查找可用的空间,之后释放Shared Pool Latdh,接下来就开始进行硬解析过程,将执行解析后的执行计划等信息记录到Library Cache中,这个过程消耗大量CPU,同时将一直持有Library Cache Latch,一直到硬解析结束(硬解析);

  • 根据获得的执行计划,剋是执行SQL,如:到Buffer Cache查询数据块等。

 

  1. 整个逻辑如下:

 

 

  1. 当出现Latch严重争用的时候:

5.1 如果同时出现大量的Shared Pool LatchLibrary Latch的话,根据上面的逻辑说明数据库中存在大量硬解析,这个时候就要查找哪些SQL是否没有绑定变量。

5.2 如果只是出现大量Library Cache Latch的话,那么可能有两种情况:

  • 当持有Library Cache Latch查找Bucket对应的Chain时候,发现存在高Version countSQL,这个时候就要扫描这些对应的子游标,整个过程一直持有Latch,导致其他会话获取不到Latch进行操作;
  • 大量的并发请求,而且不能实现SQL一次Parse Call多次Execution的情况。

 

  1. 测试模拟硬解析和SQLV而死哦那Count高的情况:

6.1 Oracle 10g以后都有方法可以让SQL产生很多的子游标,并且具备下面几种条件:

  • cursor_sharing=similar
  • 收集了列上的histogram
  • SQL中使用了此列作为条件,并且条件是“等于”
  • 这个SQL是没有绑定变量的

这时候,Oracle会认为每条SQLliteral变量都是unsafe的,因此就不重用以前的cursor,而产生一个version,重新硬解析一次。

6.2 首先创建表test1session1sid=462),然后在列上收集直方图,设置cursor_sharing=similar

SQL> select * from v$mystat where rownum=1;

 

       SID STATISTIC#      VALUE

---------- ---------- ----------

       462          0          0

 

create table test1 as select rownum as hisgron_col,rownum col from dba_objects;

 

exec dbms_stats.gather_table_stats(user,'test1',method_opt=>'for columns hisgron_col size 3');

 

SQL> select column_name,num_buckets,histogram from user_tab_col_statistics where table_name='TEST1';

 

COLUMN_NAME                    NUM_BUCKETS HISTOGRAM

------------------------------ ----------- ---------------

HISGRON_COL                              3 HEIGHT BALANCED

 

SQL> alter session set cursor_sharing=similar;

 

Session altered.

 

SQL> alter system flush shared_pool;

 

System altered.

 

6.3 开启另一个会话(session2sid=484),以上面同样的操作创建表test2

SQL> select * from v$mystat where rownum=1;

 

       SID STATISTIC#      VALUE

---------- ---------- ----------

       484          0          0

两个会话同时执行下面的存储过程,模拟并发:

Session1sid 462

  begin

     for i in 1..50000 loop

     execute immediate 'select * from test1 where hisgrom_col='||i;

     end loop;

     end;

 

Session2sid 484

SQL> begin

  2     for i in 1..50000 loop

  3     execute immediate 'select * from test2 where hisgrom_col='||i;

  4     end loop;

  5     end;

  6  /

这样写法的目的一个是为了不实现绑定变量,二是为了在Library Cache中快速产生高Version CountSQL

 

6.4 另开一个会话查看event

SQL> select sid,event,p1,p1text,p2,p2text from v$session where sid in (462,484)

 

       SID EVENT                                  P1 P1TEXT                  P2 P2TEXT

---------- ------------------------------ ---------- --------------- ---------- ---------------

       462 latch: shared pool             1611695912 address                307 number

       484 latch: shared pool             1611695912 address                307 number

       SID EVENT                                  P1 P1TEXT                  P2 P2TEXT

---------- ------------------------------ ---------- --------------- ---------- ---------------

       462 latch: shared pool             1611695912 address                307 number

       484 latch: shared pool             1611695912 address                307 number

 

SQL> /

 

       SID EVENT                                  P1 P1TEXT                  P2 P2TEXT

---------- ------------------------------ ---------- --------------- ---------- ---------------

       462 latch: row cache objects       2547220064 address                280 number

       484 latch: shared pool             1611695912 address                307 number

 

SQL> /

 

       SID EVENT                                  P1 P1TEXT                  P2 P2TEXT

---------- ------------------------------ ---------- --------------- ---------- ---------------

       462 latch: shared pool             1611695912 address                307 number

       484 latch: shared pool             1611695912 address                307 number

 

SQL> /

 

       SID EVENT                                  P1 P1TEXT                  P2 P2TEXT

---------- ------------------------------ ---------- --------------- ---------- ---------------

       462 latch: shared pool             1611695912 address                307 number

       484 latch: shared pool             1611695912 address                307 number

从上面的过程看到,大量的硬解析导致严重的latchshared pool,有一个latchrow cache objects,没有Library cache latch

 

6.5 下面看看Library Cache中的SQL情况

  1  select * from

  2  (select sql_id,child_number,child_latch,executions,sql_text from v$sql

  3  where sql_text like '%select * from test1 where hisgrom_col%'

  4  and sql_text not like '%v$sql%'

  5  and sql_text not like '%begin%'

  6  order by child_number desc)

  7* where rownum < 10

SQL> /

 

SQL_ID        CHILD_NUMBER CHILD_LATCH EXECUTIONS SQL_TEXT

------------- ------------ ----------- ---------- --------------------------------------------------

80bd9652bw00x            0           0          1 select * from test1 where hisgrom_col=48841

63axfanq9n04r            0           0          1 select * from test1 where hisgrom_col=49758

d6jrbkn0bw056            0           0          1 select * from test1 where hisgrom_col=48920

6q0x5j3h6c07a            0           0          1 select * from test1 where hisgrom_col=49215

6y1japnuuc09n            0           0          1 select * from test1 where hisgrom_col=49663

9nqm8puar00dt            0           0          1 select * from test1 where hisgrom_col=49001

dmumtqwykh0g5            0           0          1 select * from test1 where hisgrom_col=48844

dyra2169kc0n2            0           0          1 select * from test1 where hisgrom_col=48685

054uu8x52c0p1            0           0          1 select * from test1 where hisgrom_col=49695

 

9 rows selected.

 

 

SQL> select sql_id,hash_value,address,version_count from v$sqlarea where sql_id = '80bd9652bw00x';

 

SQL_ID        HASH_VALUE ADDRESS          VERSION_COUNT

------------- ---------- ---------------- -------------

80bd9652bw00x 1153302557 000000009345A768             1

 

可以看到SQLVersion_Count很高,而且V$SQL视图里面也能查到对应的子游标,为什么V$SQL最大的Child_NumberV$SQLAREA里的Version_Count值不同,应该是有些子游标被age out Share Pool的原因吧。

 

6.6 模拟高并发下对高Version Count SQL的查询:

session1sid=462中和session2sid=484中一起执行下面SQL

SQL> begin

  2  for i in 1..500000 loop

  3  execute immediate 'select * from test1 where hisgrom_col =1';

  4  end loop;

  5  end;

  6  /

 

查看session对应的event

SQL> /

 

       SID EVENT                                  P1 P1TEXT                  P2 P2TEXT

---------- ------------------------------ ---------- --------------- ---------- ---------------

       462 cursor: pin S                  1125210909 idn             2.0788E+12 value

       484 cursor: pin S                  1125210909 idn             1.9843E+12 value

 

可以看到出现大量cursor:pin s

 

 

 

 

 

 

 

 

Log Buffer相关的Latch

  1. Log Buffer相关的主要Latch有:

Latchredo copy

Latchredo allocation latch

 当一个进程在修改数据时候将会产生redo,这个redo首先在PGA中保存,然后进程需要获取redo copy latch(这个Latch的个数有隐含参数_log_simultaneous_copies决定),当获得redo copy latch后,进程接着获取redo allocation latch来分配redo log buffer中的空间,空间分配完成后,释放redo allocation latch,然后进程把PGA中临时存放的redo信息复制到redo log buffer,复制完成后,释放redo copy latch

 

 

 

 

 

 

 

 

 

 

 

附录

相关视图

V$LATCH

   shows aggregate latch statistics for both parent and child latches, grouped by latch name. 

V$LATCH_CHILDREN

   contains statistics about child latches. 1.5.3 v$latch_misses :  

   This view contains statistics about missed attempts to acquire a latch. 

V$LATCHNAME

   contains information about decoded latch names for the latches shown in V$LATCH. 

V$event_name

V$session

V$mystat

V$sql

V$sqlarea

 

 

 

我们可以通过对v$latch, v$latchholder, v$latchname的查询获得有关latch信息,例如:

/* 已知一个latch地址,找到latch名字*/

col name for a40

select a.name from v$latchname a, v$latch b

where b.addr = '&addr'

and b.latch#=a.latch#;

/* 显示系统范围内的latch统计*/

column name format A32 truncate heading "LATCH NAME"

column pid heading "HOLDER PID"

select c.name,a.addr,a.gets,a.misses,a.sleeps,

a.immediate_gets,a.immediate_misses,b.pid

from v$latch a, v$latchholder b, v$latchname c

where a.addr = b.laddr(+)

and a.latch# = c.latch#

order by a.latch#;

/* latch名称显示对latch的统计*/

select c.name,a.addr,a.gets,a.misses,a.sleeps,

a.immediate_gets,a.immediate_misses,b.pid

from v$latch a, v$latchholder b, v$latchname c

where a.addr = b.laddr(+) and a.latch# = c.latch#

and c.name like '&latch_name%' order by a.latch#;

latch40余种,但作为DBA关心的主要应有以下几种:

Cache buffers chains latch: 当用户进程搜索SGA寻找database cache buffers时需要使用此latch

Cache buffers LRU chain latch: 当用户进程要搜索buffer cache中包括所有dirty blocksLRU (least recently used)链时使用该种latch

Redo log buffer latch: 这种latch控制redo log buffer中每条redo entries的空间分配。

Row cache objects latch: 当用户进程访问缓存的数据字典数值时,将使用Row cache objects latch

下面我们将着重介绍一下如何检测和减少redo log buffer latch的冲突。对redo log buffer的访问是由redo log buffer latch来控制的,这种latch有两种类型,redo allocation latchredo copy latch

Redo allocation latch控制redo entriesredo log buffer中的空间分配。Oracle的一个用户进程只有得到redo allocation latch后才能为redo entriesredo log buffer中分配空间,又由于一个instance只有一个redo allocation latch,所以一次只有一个用户进程在buffer中分配空间。当用户进程获得latch后,首先为redo entry分配空间,然后进程继续持有latch并拷贝entrybuffer中,这种拷贝称为“在redo allocation latch上的拷贝”(copying on the redo allocation latch),拷贝完毕后,用户进程释放该latch

一个“在redo allocation latch上的拷贝”的redo entry的最大值是由初始化参数LOG_SMALL_ENTRY_MAX_SIZE定义的,根据操作系统的不同而不同。

Redo Copy Latch只应用于多CPU的系统。在多CPUinstance中,如果一个redo entry太大,超过了LOG_SMALL_ENTRY_MAX_SIZE定义值,则不能进行“在redo allocation latch上的拷贝”,此时用户进程必须获取redo copy latch。一个instance中可以有多个redo copy latch,其数目由初始参数LOG_SIMULTANEOUS_COPIES决定,缺省值为CPU数目。

在单CPU情况下,不存在redo copy latch,所有的redo entry无论大小,都进行“在redo allocation latch上的拷贝”。

redo log buffer的过多访问将导致redo log buffer latch的冲突,latch冲突将降低系统性能,我们可通过如下查询来检测这种latch冲突:

col name for a40

SELECT ln.name,gets,misses,immediate_gets,immediate_misses

FROM v$latch l,v$latchname ln

WHERE ln.name IN('redo allocation','redo copy') AND ln.latch#=l.latch#/

missesgets的比例超过1%immediate_misses(immediate_gets+immediate_misses)比例超过1%时,应考虑采取措施减少latch的冲突。

大多数的redo log buffer latch冲突是在多个CPU情况下,两个或多个Oracle进程试图同时得到相同的latch发生的。由于一个instance只有一个redo allocation latch,为减少redo allocation latch的冲突,应减少单个进程持有latch的时间,这可以通过减小初始参数LOG_SMALL_ENTRY_MAX_SIZE以减小redo entry的数目和大小来实现。如果观察到有redo copy latch冲突,可以通过增大LOG_SIMULTANEOUS_COPIES初始参数来加大latch数目,其缺省值为CPU数目,最大可增大到CPU数目的两倍。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

转载于:https://www.cnblogs.com/callmemax/p/6130619.html