Latch详解
— MaxChou
本文以学习为目的,大部分内容来自网络转载。
什么是Latch
串行化
数据库系统本身是一个多用户并发处理系统,在同一个时间点上,可能会有多个用户同时操作数据库。多个用户同时在相同的物理位置上写数据时,不能发生互相覆盖的情况,这叫做串行化。串行化会降低系统的并发性,但这对于保护数据结构不被破坏来说则是必需的。在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所需要解决的问题包括几个:
- 如何快速定位一个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的不同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
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中存在的情况
- 根据要查找的数据块的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.跳过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。 | |
- 获得第二步中查到的可以直接访问的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 Bucket的Cache Buffers Chain中去,并在Data Buffer Header中存放如下信息:
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); |
- Hash Latch,即Cache Buffers Chain Latch,Buffer Cache中Hash Bucket的个数由隐含参数_db_block_hash_buckets决定,Cache Buffers Chains Latch的个数由隐含参数_db_block_hash_latches决定。
在上面的Buffer Cache示意图中可以看到两条链(LRU和LRUW),这两条链分别将Data Buffer Header连接起来,和Cache Buffers Chain类似:
LRU和LRUW的作用:
- LRU表示Least Recently Used,也就是指最少最近使用的Data Buffer Header链表,LRU链表串联起来的Data Buffer Header都指向可用数据块(Free Buffer);
- LRUW则表示Least Recently Used Write,也叫做Dirty List,即脏数据块链表,LRUW串联起来的都是修改过但是还没有写入数据文件的数据块所对应的Data Buffer Header(Dirty Buffer);
- 一个Data Buffer Header 要么在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之后,将数据块读入到Buffer Cache,构造一个与之对应的Data Buffer Header,然后根据Hash算法(Hash Bucket=MOD(Data Block Address,_DB_BLOCK_HASH_BUCKETS)),将Data Buffer Header放到对应的Hash Bucket的Cache 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。
- 测试:模拟Cache Buffers Chains 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
- 可以看到出新latch:cache buffers chains等待事件,这就是两个会话同时要访问同一个Block,这个时候在一个会话持有Latch的时候,另一个会话必须spin等待获得Latch;
- 同时也能看到latch:library cache等待事件,这是由于在共享池进行软解玺的时候需要互动额library cache latch来扫描library cache中相应Bucket的Chain来获得执行计划来执行SQL,因为并发性高,导致library cache latch的争用;
- 我做实验的时候并没有出现而是出现下面的等待事件,也会看到latch:share pool或者cursor:pin 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出现严重争用,出新大量misses、sleeps、spin_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
- Shared Pool相关的主要Latch:
Latch:shared pool
Latch:library cache
Oracle通过Shared Pool来实现SQL共享,减少硬解析等,而SQL的相关信息,如SQL语句文本、执行计划都存放在Shared Pool的library cache部分。
- 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/
盖国强老师的博客上也有一张简洁的图:
- 通过了解SQL执行过程来了解Shared Pool的相关Lathch:
- 当客户端执行一条SQL,这时候Oracle首先将SQL文本转换成ASCII值,然后根据Hash函数计算该SQL对应的Hash Value;
- 根据得到的Hash Value到Library Cache中查找对应的Bucket,然后查找Bucket里是否存在该SQL:
(Y)->如果存在,则接下来查找对应的子游标,这个时候将一直持有Library Cache Latch,直到找到对应的执行计划,然后释放Latch(软解析);
(N)->如果不存在,就要去Shared Pool里面获得可用空间,来生成对应的Library Cache对象,这个时候就要获得Shared Pool Latch在Shared Pool的Free list(Shared Pool通过Free List管理Free Chunk)查找可用的空间,之后释放Shared Pool Latdh,接下来就开始进行硬解析过程,将执行解析后的执行计划等信息记录到Library Cache中,这个过程消耗大量CPU,同时将一直持有Library Cache Latch,一直到硬解析结束(硬解析);
- 根据获得的执行计划,剋是执行SQL,如:到Buffer Cache查询数据块等。
- 整个逻辑如下:
- 当出现Latch严重争用的时候:
5.1 如果同时出现大量的Shared Pool Latch和Library Latch的话,根据上面的逻辑说明数据库中存在大量硬解析,这个时候就要查找哪些SQL是否没有绑定变量。
5.2 如果只是出现大量Library Cache Latch的话,那么可能有两种情况:
- 当持有Library Cache Latch查找Bucket对应的Chain时候,发现存在高Version count的SQL,这个时候就要扫描这些对应的子游标,整个过程一直持有Latch,导致其他会话获取不到Latch进行操作;
- 大量的并发请求,而且不能实现SQL一次Parse Call多次Execution的情况。
- 测试模拟硬解析和SQL的V而死哦那Count高的情况:
6.1 Oracle 10g以后都有方法可以让SQL产生很多的子游标,并且具备下面几种条件:
- cursor_sharing=similar
- 收集了列上的histogram
- SQL中使用了此列作为条件,并且条件是“等于”
- 这个SQL是没有绑定变量的
这时候,Oracle会认为每条SQL的literal变量都是unsafe的,因此就不重用以前的cursor,而产生一个version,重新硬解析一次。
6.2 首先创建表test1(session1:sid=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 开启另一个会话(session2:sid=484),以上面同样的操作创建表test2:
SQL> select * from v$mystat where rownum=1;
SID STATISTIC# VALUE
---------- ---------- ----------
484 0 0
两个会话同时执行下面的存储过程,模拟并发:
Session1:sid 462
begin
for i in 1..50000 loop
execute immediate 'select * from test1 where hisgrom_col='||i;
end loop;
end;
Session2:sid 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 Count的SQL。
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
从上面的过程看到,大量的硬解析导致严重的latch:shared pool,有一个latch:row 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
可以看到SQL的Version_Count很高,而且V$SQL视图里面也能查到对应的子游标,为什么V$SQL最大的Child_Number和V$SQLAREA里的Version_Count值不同,应该是有些子游标被age out Share Pool的原因吧。
6.6 模拟高并发下对高Version Count SQL的查询:
在session1:sid=462中和session2:sid=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
- Log Buffer相关的主要Latch有:
Latch:redo copy
Latch:redo 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#;
latch有40余种,但作为DBA关心的主要应有以下几种:
Cache buffers chains latch: 当用户进程搜索SGA寻找database cache buffers时需要使用此latch。
Cache buffers LRU chain latch: 当用户进程要搜索buffer cache中包括所有dirty blocks的LRU (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 latch和redo copy latch。
Redo allocation latch控制redo entries在redo log buffer中的空间分配。Oracle的一个用户进程只有得到redo allocation latch后才能为redo entries在redo log buffer中分配空间,又由于一个instance只有一个redo allocation latch,所以一次只有一个用户进程在buffer中分配空间。当用户进程获得latch后,首先为redo entry分配空间,然后进程继续持有latch并拷贝entry到buffer中,这种拷贝称为“在redo allocation latch上的拷贝”(copying on the redo allocation latch),拷贝完毕后,用户进程释放该latch。
一个“在redo allocation latch上的拷贝”的redo entry的最大值是由初始化参数LOG_SMALL_ENTRY_MAX_SIZE定义的,根据操作系统的不同而不同。
Redo Copy Latch只应用于多CPU的系统。在多CPU的instance中,如果一个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#/
若misses与gets的比例超过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