oracle latch工作原理

       latch是一种轻量级用于保护oracle共享内存结构,用户并发操作一致性的串行化锁定机制,如SGA中,各种数据被反复从磁盘读取到内存,又被重新写回到磁盘上,如果有并发用户做相同的事情,oracle必须使用一种机制来保证数据在读取的时候,只能由一个会话来完成,这就是latch,latch 不会造成阻塞,是只会等待,与每个latch相联系的还有一个清楚过程,当持有latch的进程成为死进程时,系统清除过程就会被调用,系统lock导致 用户等待,需要考虑系统的逻辑设计是否有问题,如多用户对主键的删除或者修改,是否有用户使用select… for update这样的语法,外键是否创建索引。latch 争用多半要考虑系统及数据库自身设计问题,如绑定变量,热块及参数设置是否合理。
  spin:比如数据缓存中的某个块要被读取,我们会获得这个块的 latch,这个过程叫做spin,另外一个进程恰好要修改这个块,他也要spin这个块,此时他必须等待,当前一个进程释放latch后才能spin 住,然后修改,如果多个进程同时请求的话,他们之间将出现竞争,没有一个入队机制,
  一旦前面进程释放所定,后面的进程就蜂拥而上,没有先来后到的概念,并且这一切都发生的非常快,因为Latch的特点是快而短暂。
  spin与休眠:
  休眠意味着暂时的放弃CPU,进行上下文切换(context switch),这样CPU要保存当前进程运行时的一些状态信息,比如堆栈,信号量等数据结构,然后引入后续进程的状态信息,处理完后再切换回原来的进程 状态,这个过程如果频繁的发生在一个高事务,高并发进程的处理系统里面,将是个很昂贵的资源消耗,所以Oracle选择了spin,让进程继续占有 CPU,运行一些空指令,之后继续请求,继续spin,直到达到_spin_count值,这时会放弃CPU,进行短暂的休眠,再继续刚才的动作。
  oracle中,latch是一种轻量级的锁。一般来说,latch由三种内存元素成:pid(进程id),内存地址和内存长度。Latch保证对共享数 据结构的排它性访问,以此来保证内存结构的完整性不受到损坏。在多个会话同时修改或者检视(inspect)sga中同一个内存结构时,必须串行化访问以 保证sga中数据结构的完整性。
  进程获取latch有两种模式:willing-to-wait和No_wait。no-wait模式只在少数latch中使用。通过no-wait模式 获取latch的统计信息记录在immediate_gets immediate_misses列中,这些列在v$latch,v$latch_parent,v$latch_children视图中都存在。一般来 说,no-wait模式在第一次获取一些有很多子latch的latch比如redo copy时使用。如果一个进程第一次获取这些子latch中的任何一个失败,它会立即使用no-wait模式询问下一个。只有当采用no-wait模式试 图获取所有的子latch都失败以后,才会转而采用willing-to-wait模式。
  通过willing-to-wait模式获取latch的统计信息存放在gets和misses列中。每当一个进程用willing-to-wait模式去获取一个latch时,gets都会增加。
  如果进程在第一次请求latch时,latch可用,就会直接获得该latch。在修改任何受到保护的数据结构之前,进程会将一些恢复信息写入到latch恢复区,这样当获得latch的进程发生异常时,pmon进程才能够清理该进程持有的latch。
  如果请求latch时,该latch不可用,进程就会在cpu中等待一小段时间(spin)然后重新请求latch。如果latch一直不可用,该过程 (spin一段时间然后重新请求)会一直重复。重复的次数由隐含参数_spin_count决定,默认值2000。如果在请_spin_count次之内 获得了latch,就对spin_gets和misses列各加一,否则,进程v$session_wait中记录latch free等待事件,然后释放cpu,转入睡眠状态。睡眠一定时间后,进程被唤醒并重复上面的过程,一直到获得latch。在成功获得latch后,才会更 行sleep列得统计信息。
  由于进程只有在获得latch后才会停止对latch得请求,如果某个持有latch的进程发生异常,其他请求该latch的进程该怎么办?岂不是要一直 等待下去?不会的。当一个进程请求latch失败一定次数后,它会请求pmon进程查看该latch的持有者,如果持有进程异常,pmon就会清理该进 程,释放latch。
  每个latch都有一个从0到13的优先级编号。父latch和独立latch的优先级编号是在oracle内核代码中固定的。子latch是÷在实例启动时创建,其优先级编号从其父latch继承。使用优先级可以避免死锁。
  当一个进程请求no-wait模式的latch时,该latch的优先级编号必须和它当前已经持有的latch的优先级编号相同。
  当一个进程请求willing-to-wait模式的latch时,该latch的优先级编号必须比它当前已经持有的latch的优先级编号要大。
  进程获取Latch的过程:
  任何时候,只有一个进程可以访问内存中的某一个数据块,如果进程因为别的进程正占用块而无法获得Latch时,他会对CPU进行一次spin(旋转),时 间非常的短暂,spin过后继续获取,不成功仍然spin,直到 spin次数到达阀值限制(这个由隐含参数_spin_count指定),此时进程会停止spin,进行短期的休眠,休眠过后会继续刚才的动作,直到获取 块上的Latch为止。进程休眠的时间也是存在算法的,他会随着spin次数而递增,以厘秒为单位,休眠的阀值限制由隐含参数 _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争用
  访问频率非常高的数据块被称为热快(Hot Block),当很多用户一起去访问某几个数据块时,就会导致一些Latch争用,最常见的latch争用有:
  (1)  buffer busy waits
  (2)  cache buffer chain
  Cache buffer chian产生原因:
  当一个会话需要去访问一个内存块时,它首先要去一个像链表一样的结构中去搜索这个数据块是否在内存中,当会话访问这个链表的时候需要获得一个Latch,如果获取失败,将会产生Latch cache buffer chain 等待,导致这个等待的原因是访问相同的数据块的会话太多或者这个列表太长(如果读到内存中的数据太多,需要管理数据块的hash列表就会很长,这样会话扫描列表的时间就会增加,持有chache buffer chain latch的时间就会变长,其他会话获得这个Latch的机会就会降低,等待就会增加)。
  Buffer busy waits 产生原因:
  当一个会话需要访问一个数据块,而这个数据块正在被另一个用户从磁盘读取到内存中或者这个数据块正在被另一个会话修改时,当前的会话就需要等待,就会产生一个buffer busy waits等待。
  产生这些Latch争用的直接原因是太多的会话去访问相同的数据块导致热快问题,造成热快的原因可能是数据库设置导致或者重复执行的SQL 频繁访问一些相同的数据块导致。
  查看造成LATCH BUFFER CACHE CHAINS等待事件的热快
  select distinct a.owner, a.segment_name
  from dba_extents a,
  (select dbarfil, dbablk
  from x$bh
  where hladdr in (select addr
  from (select addr
  from v$latch_children
  order by sleeps desc)
  where rownum<20)) b
  where  a.relative_fno=b.dbarfil
  and a.block_id<=b.dbablk
  and a.block_id+a.blocks>b.dbablk;
  查询当前数据库最繁忙的Buffer,TCH(Touch)表示访问次数越高,热点快竞争问题就存在
  select *
  from (select addr,
  ts#,
  file#,
  dbarfil,
  dbablk,
  tch
  from x$bh
  order by tch desc)
  where rownum<11;
  查询当前数据库最繁忙的Buffer,结合dba_extents查询得到这些热点Buffer来自哪些对象
  select e.owner, e.segment_name, e.segment_type
  from dba_extents e,
  (select *
  from (select addr, ts#, file#, dbarfil, dbablk, tch
  from x$bh
  order by tch desc)
  where rownum<11) b
  where e.relative_fno=b.dbarfil
  and e.block_id<=b.dbablk
  and e.block_id+e.blocks>b.dbablk;
  如果在Top 5中发现latch free热点块事件时,可以从V$latch_children中查询具体的子Latch信息
  select *
  from (select addr, child#, gets, misses, sleeps, immediate_gets igets,
  immediate_misses imiss, spin_gets sgets
  from v$latch_children
  where name= ‘cache buffers chains’
  order by sleeps desc)
  where rownum<11;
  获取当前持有最热点数据块的Latch和buffer信息
  select b.addr, a.ts#, a.dbarfil, a.dbablk, a.tch, b.gets, b.misses, b.sleeps
  from (select *
  from (select  addr, ts#, file#, dbarfil, dbablk, tch, hladdr
  from x$bh
  order by tch desc)
  where rownum<11) a,
  (select addr, gets, misses, sleeps
  from v$latch_children
  where name= ‘cache buffers chains’) b
  where a.hladdr = b.addr;
  利用前面的SQL可以找到这些热点Buffer的对象信息
  select distinct e.owner, e.segment_name, e.segment_type
  from dba_extents e,
  (select *
  from (select addr, ts#, file#, dbarfil, dbablk, tch
  from x$bh
  order by tch desc)
  where rownum<11) b
  where e.relative_fno = b.dbarfil
  and e.block_id<=b.dbablk
  and e.block_id+e.blocks>b.dbablk;
  结合SQL视图可以找到操作这些对象的相关SQL,然后通过优化SQL减少数据的访问,或者优化某些容易引起争用的操作(如connect by等操作)来减少热点块竞争
  break on hash_value skip 1
  select /*+ rule */ hash_value,sql_text
  from v$sqltext
  where (hash_value, address) in (
  select a.hash_value, a.address
  from v$sqltext a,
  (select distinct a.owner, a.segment_name, a.segment_type
  from dba_extents a,
  (select dbarfil, dbablk
  from (select  dbarfil, dbablk
  from x$bh
  order by tch desc)
  where rownum<11) b
  where a.relative_fno = b.dbarfil
  and a.block_id <= b.dbablk
  and a.block_id + a.blocks > b.dbablk) b
  where a.sql_text like ‘%’ || b.segment_name || ‘%’
  and b.segment_type = ‘TABLE’)
  order by  hash_value, address, piece;

posted on 2013-11-12 22:34  heartstage  阅读(227)  评论(0编辑  收藏  举报

导航