Latch

来源:互联网 发布:js闭包的概念 编辑:程序博客网 时间:2024/04/29 09:12

   一、latch的基本原理

   数据库系统本身是一个多用户并发处理系统,在同一个时间点上,可能会有多个用户同时操作数据库。这里就涉及两个很重要的问题。

   这些用户之间的操作不会互相破坏。比如两个用户同时在相同的物理位置上写数据时,不能发生互相覆盖的情况。这叫串行化,也就是说,即便两个用户同时 写,也必须有先后,一个用户写完,另一个用户继续写。串行化会降低系统的并发性,但这对于保护数据结构不被破坏来说则是必需的。
  在满足串行化的前提下,如何将并发性提升到最大呢?在Oracle数据库中,通过闩锁(latch)和锁定(lock)来解决这两个问题。闩锁和锁定既有相同点又有不同点。相同点在于它们都是用于实现串行化的资源。而不同点则在于闩锁是一个低级别、轻量级的锁,获得和释放的速度很快,以类似于信号灯的方式实现。而锁定则可能持续的时间很长,通过使用 队列,按照先进先出的方式实现。也可以简单地理解为闩锁是微观领域的,而锁定则是宏观领域的。
   Oracle数据库使用闩锁来管理内存的分配和释放。假设,某个用户进程(假设其为A)发出一条update语句,要去更新58号数据块里的某条记 录。则该用户进程对应的服务器进程在写内存的时候,找到58号数据块,并往里写内容。A在写58号数据块的过程中,这时,另一个用户进程B发出 insert语句,要将某个新的记录插入到58号数据块里。如果没有一定的保护机制,A正要写入的空间可能会被B抢先写入,或者相反,B正要写入的空间也 可能会被A抢先写入。不管哪个用户先抢先写入,造成的结果就是,58号数据块里的数据都混乱了,因为这时,A和B之间的数据互相交织在一起了。因此,必须使用latch对此进行保护。简单来说,任何进程要写数据块时,都必须先获得latch,在写入过程中,一直持有该latch,写完以 后,释放该latch。对于上面的例子来说,当A在写入58号数据块时,先获得latch,然后开始写。而当A正在写入的过程中,B也要写58号数据块。 这时B在尝试获得latch时,发现该latch正被其他用户(也就是A)持有,因此B进入等待状态。直到A写完数据块并释放latch以后,B才能获得 latch,获得latch以后,才能在58号数据块里写入数据。
   这里只是以写数据块为例来说明为何要使用latch。而事实上,latch不仅仅用于写数据块,比如对于shared pool来说,其内存单位就不是数据块了。latch也不仅仅用于写操作,只要涉及内存地址的读和写,都需要通过获得latch来实现串行化,一次只能有 一个服务器进程在读或者写内存地址。
  Oracle在实例管理中,不管是buffer cache、shared pool还是log buffer,都引入了各种各样的latch。
实现latch时,实际是由操作系统的旗语(semaphore:也叫信号量)来完成的。为了便于理解,可以把它们想象为,通过某个变量值的变化而 实现的。变量值为0则说明latch当前没有被其他进程获取,否则如果为非0值,则说明它已经被其他进程所获取了。Oracle在设计latch的时候将 其定义为轻量级锁,因此它的操作非常快,以微秒(microsecond,也就是百万分之一秒)来计算。
latch分为两种类型:
愿意等待(Willing-To-Wait)
大部分的latch都属于这种类型。这种类型的latch都是通过Test-And-Set的方式来实现的。也就是说,如果当前进程不能获得 latch的时候,会绕着CPU旋转,而不放弃CPU。这也就是所谓的SPIN CPU,实际就是执行一段空循环,类似执行下面一段代码(其中的N由Oracle内部来控制)
   
Loop        exit when i>= N        i := i+1;        null;end loop;


   进程之所以不释放CPU而是绕着CPU旋转,是由于latch操作本身是一个很快速的动作,因此可能等一会就能获得latch了。当进程一旦获得 CPU,但是获得不了latch时,如果这时候立刻放弃CPU,那么需要进行上下文切换,下次再次尝试获得latch时,又要进行上下文切换,可能反而要 消耗更多的时间。因此,进程在不能获得latch的时候,会执行上面这段代码,绕着CPU转一会,然后再次尝试获得latch,如果仍然不能获得,则再次 旋转CPU。当反复旋转CPU并尝试获得latch的的次数超过某个上限(该上限由隐藏参数控制)时,这时进程会释放CPU,并进入睡眠(Sleep)状 态。进程一旦进入睡眠状态,则会抛出一个对应的等待事件,并记录在视图v$session_wait里,说明当前该进程正在等待的latch的类型等信 息。初始状态下,一个进程会睡眠0.01秒。然后醒过来,并再次尝试获得latch。如果旋转CPU的次数达到上限以后,仍然不能获得latch,则再次 进入睡眠,这时会睡眠两倍的时间,依此类推,直到达到睡眠的最大值:0.2秒。
   这是在数据库服务器具有多个CPU时的情形,如果只有一个CPU,就不存在旋转CPU的情况,一旦获得不了latch,就进入睡眠。总的来说,当进程尝试获取Willing-To-Wait类型的latch时,如果失败,则进程会一直尝试对latch的获取,不断循环,直到获得 latch为止,或者是达到所指定的上限值为止。当达到上限值时,进程进入睡眠。
不等待(No-Wait)
这种类型的latch比较少,对于这种类型的latch来说,都会有很多个可用的latch。当一个进程请求其中的一个latch时,会以no- wait模式开始请求。如果所请求的latch不可用,则进程不会等待,而是立刻请求另外一个latch。只有当所有的latch都不能获得时,才会进入 等待。
从另外一个角度来说,latch分为单个latch(Solitary latch,比如shared pool latch以及redo allocation latch等)和latch组(比如library cache latch、cache buffers lru chain latch以及cache buffers chains latch等)。latch组包括父latch和子latch。单个latch和父latch都是定义在数据库软件代码里的,而且都是静态分配的。对于每 种类型的latch,只有一个父latch。而子latch则根据参数或默认值而动态设定,而且子latch的访问独立于父latch。通常来说,父 latch只用于汇总显示报表的目的。
如果latch资源被争用,通常都会表现为CPU资源使用过高。而反过来说,如果我们发现CPU资源很紧张,利用率总是在90%以上,甚至总是在 100%,其主要原因有以下几点。
SQL语句没有使用绑定变量。如果没有使用绑定变量,或者书写SQL时随意性过大,比如大小写混用等。则Oracle对每一条SQL语句都要进行解 析,也就是要非常频繁地读写shared pool里的内存块,从而导致与解析SQL相关的latch争用。
执行SQL语句时,扫描的数据块过多,或者说SQL语句写的比较低效,导致要扫描很多的数据块才能返回所要的记录。因为在查找、扫描数据块的过程 中,进程也要获得latch,直到找到数据块为止。
为何一旦latch资源发生争用,就会导致CPU繁忙呢?可以想象一下,假设某个进程(A)执行一条SQL语句需要访问10000个数据块,那么该 进程在扫描数据块的过程中,一直持有latch。而另一个进程B也要执行SQL,但是由于A持有了latch,导致B无法获得,于是旋转一会CPU,再去 获得latch,直到进入睡眠才释放CPU。接下来C进程也要执行SQL,同样的,由于A持有了latch,导致C无法获得,于是也旋转一会CPU,再去 获得latch,直到进入睡眠才释放CPU。如果类似B和C的进程很多的话,那我们会发现,CPU总是在被旋转,也就是在做空的循环,而无法做其他的事 情。因此,体现出CPU的使用率过高。
要解决latch的争用,关键在于共享SQL语句(比如使用绑定变量、规范SQL的书写等)以及优化SQL语句,使其搜索以及扫描的数据块的个数下 降到最低。


二、latch free

    Latch Free(闩锁释放):
Latch Free通常被称为闩锁释放,这个名称常常引起误解,实际上我们应该在前面加上一个“等待”(wait),当数据库出现这个等待时,说明有进程正在等待某个Latch被释放,也就是waiting latch free。
Latch是一种低级排队(串行)机制,用于保护 SGA中共享内存结构。Latch就像是一种快速被获取和释放的内存锁,用于防止共享内存结构被多个用户同时访问。其实不必把 Latch想得过于复杂,Latch通常就是操作系统利用内存中的某个区域,通过设置变量为0或非0,来表示Latch是否已经被取得,大多数操作系统, 是使用TEST AND SET的方式来完成Latch检查或持有的。

在数据库内部,Oracle通过v$latch视图记录不同类型Latch的统计数据,按获取和等待方式不同进行分类,Latch请求的类型可以分 为willing-to-wait和immediate两类。
•willing-to-wait:是指如果所请求的Latch不能立即得到,请求 进程将等待一段很短的时间后再次发出请求。进程一直重复此过程直到得到Latch。
•immediate:是指如果所请求的Latch不能立即得 到,请求进程就不再等待,而是继续执行下去。

在v$latch中以下字段记录了willing-to-wait请求。
• GET:成功地以willing-to-wait请求类型请求一个Latch的次数。
• MISSES:初始以willing-to-wait请求类型请求一个Latch不成功,而进程进入等待的次数。
• SLEEPS:初始以willing-to-wait请求类型请求一个Latch不成功后,进程等待获取Latch时进入休眠的次数。

在 v$latch中以下字段记录了immediate类请求。
• IMMEDIATE_GETS:以immediate请求类型成功地获得一个Latch的次数。
• IMMEDIATE_MISSES:以immediate请求类型请求一个Latch不成功的次数。


最常见的Latch集中于Buffer Cache的竞争和Shared Pool的竞争。和Buffer Cache相关的主要Latch竞争有cache buffers chains和cache buffers lru chain,和Shared Pool相关的主要Latch竞争有Shared Pool Latch和Library Cache Latch等。

Buffer Cache的Latch竞争经常是由于热点块竞争引起;Shared Pool的Latch竞争通常是由于SQL的变量硬解析引起。



整理自网络