当前位置:Gxlcms > 数据库问题 > Oracle Latch的学习【原创】

Oracle Latch的学习【原创】

时间:2021-07-01 10:21:17 帮助过:12人阅读

                                                                                                                                                   — 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的争用;
  • 我做实验的时候并没有出现而是出现下<

人气教程排行