时间:2021-07-01 10:21:17 帮助过:12人阅读
— MaxChou
本文以学习为目的,大部分内容来自网络转载。
串行化
数据库系统本身是一个多用户并发处理系统,在同一个时间点上,可能会有多个用户同时操作数据库。多个用户同时在相同的物理位置上写数据时,不能发生互相覆盖的情况,这叫做串行化。串行化会降低系统的并发性,但这对于保护数据结构不被破坏来说则是必需的。在Oracle数据库中,通过闩锁(latch)、锁定(lock)、互斥(mutex)来实行串行化,保护数据结构一致性的。
Latch的定义和作用
Oracle数据库使用闩锁(latch)来管理SGA内存的分配和释放,Latch是用于保护SGA中共享数据结构的一种串行化锁定机制。Latch的实现是与操作系统相关的,尤其和一个进程是否需要等待一个latch、需要等待多长时间有关。
Latch是一种能够极快地被获取和释放的锁,它通常用于保护描述buffer cache中block的数据结构。
比如数据缓存中的某个块要被读取,我们会获得这个块的latch,这个过程叫做pin;另外一个进程恰好要修改这个块,他也要pin这个块,此时他必须等待。当前一个进程释放latch后才能pin住,然后修改。如果多个进程同时请求的话,他们之间将出现竞争,没有一个入队机制,一旦前面进程释放latch,后面的进程就蜂拥而上,没有先来后到的概念,这个和Lock是有本质区别的。这一切都发生的非常快,因为Latch的特点是快而短暂,当然这个只是大致过程。
如何获取Latch
任何时候只有一个进程可以访问内存中的某一个块,如果进程因为别的进程正占用块而无法获得Latch时,他会对CPU进行一次spin(旋转),时间非常的短暂,spin过后继续获取,不成功仍然spin,直到 spin次数到达阀值限制(这个由隐含参数_spin_count指定),此时进程会停止spin,进行短期的休眠,休眠过后会继续刚才的动作,直到获取块上的Latch为止。进程休眠的时间也是存在算法的,他会随着spin次数而递增,以厘秒为单位,如1,1,2,2,4,4,8,8...休眠的阀值限制由隐含参数_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 latch和library cache latch是采用这个机制,如果将隐含参数_latch_wait_posting设置为2,则所有Latch都采用这种等待方式,使用这种方式能够比较精确的唤醒某个等待的进程,但维护Latch Wait List需要系统资源,并且对Latch Wait List上Latch的竞争也可能出现瓶颈。
如果一个进程请求、旋转、休眠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 pool,library cache,cache buffer chains,buffer busy wait。
Oracle使用两种数据结构来进行shared pool的并发控制:lock和pin。Lock比pin具有更高的级别,Lock在handle上获得,在pin一个对象之前,必须首先获得该handle的lock。
Lock主要有三种模式:Null、share、Exclusive。在读取访问对象时,通常需要获取Null(空)模式以及share(共享)模式的锁定;在修改对象时,需要获得Exclusive(排他)锁定。
在锁定了Library Cache对象以后,一个进程在访问之前必须pin该对象。同样pin有三种模式:Null、shared、exclusive。只读模式时获得共享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所需要解决的问题包括几个:
因为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的目的,oracle使用hash buckets的结构来组织data
buffer header,通过对data buffer header的不同rdba和class做HASH算法来实现对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 block在oracle中可能会有多个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
Latch:cache buffers lru chain
Latch:cache 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中存在的情况
在Cache Buffers Chain上查找的具体逻辑如下,根据我手画的图理解:
1.比较Data Buffer Header上所记录的Block地址,不符合条件的就跳过此Data Buffer Header; |
|
2.跳过status为CR的Data 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中的内容构建一个xcurrent的Buffer和一个CR状态的Data Buffer Header(指向新建立xcurrent状态的复制Buffer)。 |
|
6.搜索完整个Hash Chain还未发现需要的Data Buffer Header,从disk读取数据块,读入到Buffer Cache中,相应的Data Buffer Header挂在Cache Buffers Chain上。 |
|
Ps: Buffer Header指向的Buffer的6种状态:(可以参照V$BH的status,v$bh:非常详细地记录了数据块在数据缓冲区内的使用情况,一条记录对应一个block的详细记录。 v$bh来自于基表x$bh与x$le) |
1. Free:可以被重用的Block; |
2. xcurrent:以exclusive方式获取的当前模式的Block(insert、update、delete时产生), Scurrent: 可以与其他instance共享的当前模式Block; |
|
3. CR:一致读块,永远不会写入disk; |
|
4. reading:正从disk读取出来的块; |
|
5. mreciver:正在进行介质恢复的Block; |
|
6. ircovery:正在进行instance recovery的Block。 |
N->如果数据块在Buffer Cache中不存在(在Hash Chain中查找不到对应Data Buffer
Header)
如何才能快速查询到数据块
Buffer Cache是很大的,如果一个Buffer一个Buffer的扫描是相当耗费资源和查询时间的,所以要通过类似目录(Hash Bucket)的方法,让数据库能快速定位数据块的位置。
下面是关于Buffer Cache的示意图:
先看以下几点:
1.存放该Block在Buffer 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); |
在上面的Buffer Cache示意图中可以看到两条链(LRU和LRUW),这两条链分别将Data Buffer Header连接起来,和Cache Buffers Chain类似:
LRU和LRUW的作用:
所以当查找数据块在Buffer Cache中不存在的时候(即Hash Chain中查找不到对应的
Data Buffer Header的情况下):就要扫描LRU List寻找Free的Buffer,在扫描过程将
持有Cache Buffers Lru Chain Latch(其Latch数量由隐含参数_db_block_lru_latches决
定),扫描过程中会把已经修改过的Buffer移动到LRUW链表上;
在Buffer Cache中查找数据块的总流程:
可能导致Latch争用的两种情况
A. 某一进程过长时间的持有Latch,导致其他进程不能正常的得到Latch,只能等待;
B. 可能存在的大量的Latch请求。
4.1 如果出现Cache Buffers Chains Latch严重争用,根据以上原理,那么可能有如下原因:
4.2 如果出现Cache Buffers Lru Chain Latch严重争用,那么可能有如下原因:
5.1 创建表test,总共一条记录,共1个Block
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