Latch闩锁在Oracle中属于 KSL Kernel Services Latching, 而从顶层视图来讲 KSL又属于VOS Virtual Operating System。html
Latches 是一种 低级别(low-level)的 锁机制, 初学IT的同窗请注意 低级不表明简单, C语言对比java语言要 低级一些但C并不比java简单。java
在一些文章著做中也将latch称为spin lock 自旋锁。 latch用来保护 共享内存(SGA)中的数据 以及关键的代码区域。 通常咱们说有2种latch:编程
1)Test and Set 简称TAS :
数据结构
TAS是计算机科学中的专指, test-and-set instruction 指令 用以在一个 原子操做(atomic 例如非中断操做)中写入到一个内存位置 ,并返回其旧的值。 常见的是 值1被写入到该内存位置。 若是多个进程访问同一内存位置, 如有一个进程先开始了test-and-set操做,则其余进程直到第一个进程结束TAS才能够开始另外一个TAS。 多线程
关于TAS指令更多信息 能够参考wiki ,包括TAS的伪代码例子: http://t.cn/zQgATRr 并发
askmaclean.comoracle
在Oracle中Test-And-Set类型的latch使用原生的Test-And-Set指令。 在绝大多数平台上, 零值zero表明latch是 空闲或者可用的 , 而一个非零值表明 latch 正忙或者被持有。 可是仅在HP PA-RISC上 正相反。 TAS latch只有2种状态 : 空闲 或者 忙。ide
2) Compare-And-Swap 简称 CAS
函数
Compare-And-Swap 也是计算机专有名词, Compare-And-Swap(CAS)是一个用在多线程环境中实现同步的 原子指令( atomic )。 该指令将在一个给定值(given value)和 指定内存位置的内容 之间比对,仅在一致的状况下 修改该内存位置的内容为一个 给定的 新值(不是前面那个值)。 这些行为都包含在一个 单独的原子操做中。 原子性保证了该新的值是基于最新的信息计算得到的; 若是该 内存位置的内容在同时被其余线程修改过,则本次写入失败。 该操做的结果必须说明其究竟是否执行了 取代动做。 它要么返回一个 布尔类型的反馈, 要么返回从 指定内存地址读取到的值(而不是要写入的值)。post
关于CAS的更多信息能够参考 http://t.cn/hcEqh
Oracle中的 Compare-And-Swap Latch也使用原生态的Compare-And-Swap指令。 和TAS Latch相似, 空值表明latch是free的,而一个非空值表明latch正忙。 可是一个CAS latch 能够有多种状态 : 空闲的、 以共享模式被持有 、 以排他模式被持有。 CAS latch能够在同一时间被 多个进程或线程以共享模式持有, 但仍是仅有一个进程能以排他模式持有CAS latch。 典型的状况下, 共享持有CAS latch的进程以只读模式访问相关数据, 而一个排他的持有者 目的显然是要写入/修改 对应CAS latch保护的数据。
举例来讲, CAS latch的共享持有者是为了扫描一个链表 linked list , 而相反排他的持有者是为了修改这个列表。 共享持有者的总数上线是0x0fffffff即10进制的 268435455。
注意几乎全部平台均支持CAS latch, 仅仅只有HP的PA-RISC平台不支持(惠普真奇葩)。 在PA-RISC上CAS latch实际是采用TAS latch。 因此虽然在HP PA-RISC上代码仍会尝试以共享模式得到一个latch,可是抱歉最终会以 排他模式得到这个latch。
通常 一个latch会包含如下 信息:
Latch type 类型 , latch type定义了 是TAS 仍是CAS latch, latch class和 latch number
Latch的 level 级别
持有该latch的代码位置where ,例如 使用kslgetl函数得到某个latch,则持有文职为kslgetl
持有该latch的缘由
nowait模式下得到该latch的次数 V$LATCH.IMMEDIATE_GETS
wait模式下第一个尝试失败的次数 V$LATCH .MISSES
nowait模式下尝试失败的次数 V$LATCH.IMMEDIATE_MISSES
获取latch失败形成sleep的总时间 X$KSLLTR.KSLLTWSL, V$LATCH.SLEEPS
首次spin成功得到latch的次数 X$KSLLTR.KSLLTHST0, V$LATCH.SPIN_GETS
latch wait list等等
子闩 child latch
当一个单一的latch要保护过多的资源时 会形成许多争用, 在此种场景中 child latch变得颇有用。 为了使用child latch, 须要分割原latch保护的资源为多个分区, 最多见的例子是 放入到多个hash buckets里, 并将不一样子集的资源分配给一个child latch。 比起每个hash bucket都去实现一个单独的latch来讲, 编程上 使用child latch要方便的多, 虽然这不是咱们用户所须要考虑的。 为一个latch 定义多个child latch,则这个latch称为parent latch父闩。 child latch 能够继承 parent latch的一些属性, 这些属性包括 级别和清理程序。 换句话说, child latch就像是parent 父闩的拷贝同样。
经典状况下, 在SGA 初始化过程当中child latch将被分配和初始化(startup nomount)。但在目前版本中(10/11g)中也容许在实例启动后 建立和删除latch。
child latch又能够分红2种:
容许一个进程/线程在同一时刻持有2个兄弟child latch
不容许一个进程/线程在同一时刻持有2个兄弟child latch
由于child latch从parent latch那里继承了属性,因此注意 child latch的 latch level和 parent 父闩是同样的。 由于 一个进程/线程 不能在同一时间 持有2个latch level同样的闩,因此正常状况下 一个进程/线程 也不能同一时间 持有2个兄弟child latch。
回到咱们说的hash bucket的例子里来, 假设一个进程/线程有 将一个resource从一个hash bucket 移动到另外一个hash bucket的需求,在此场景中就须要 同时持有2个兄弟child latch。 可是若是容许这种同时持有2个兄弟child latch的行为发生的话, 那么很容易形成死锁deadlock的麻烦。 oracle 不容许 进程/线程任意地同时得到2个兄弟child latch,因为此种操做很容易引发死锁。 由此引入了一些规则 : 兄弟child latch必须是相关的child number ,且进程/线程只能以特性的顺序来同时get 2个兄弟child latch,即child number 从大到小低贱的顺序。
此外须要注意的是仅有TAS latch能够同时get多个兄弟child latch,目前还不支持 CAS的latch。
Latch 清理恢复
Oracle中定义了一个latch, 就须要这个latch对应的清理函数cleanup function,这个函数在如下2个场景中生效:
当某个latch被持有,可是持有进程遇到了某一个错误 ==》主动
当持有latch的进程die掉,须要PMON进程前去恢复这个latch的状态 ==》被动
经典状况下, 执行清理函数的进程要么把正在执行过程当中的操做回滚掉 ,要么前滚掉。 为了为 前滚(rolling forward)或者回滚(rolling back)提供必要的信息, oracle在latch结构中加入了recovery的结构,其中包含了这个正在执行过程当中的操做的日志信息, 这些日志信息必须包含足之前滚或者回滚的数据。 如咱们之前讲过的, 理论上oracle进程可能在运行任何指令的时候意外终止,因此清理恢复是常事。
清理恢复最恶心的bug是PMON 执行cleanup function时由于 代码bug ,把PMON本身给弄dead了, 因为PMON的关键的后台进程,因此这会引发实例终止。
Latch和 10.2.0.2后引入的KGX Mutex对比
和 latch同样, kgx mutex也是用来控制串行化访问SGA中数据的 ,但仍有一些重要区别:
KGX mutex要比 CAS latch更轻量级, mutex 结构大约为16个字节, 而一个latch结构大约是100个字节。 所以 mutex嵌入到大量其余对象结构中是可行的, 由于他的struct 足够小
之因此mutex能够提供 更小的结构 很廉价的成本,其缘由是 使用mutex有一个简单的前提假设: 对于mutex的争用是很小的。 所以没有为mutex那样提供一个优化过的wait list , mutex作更多的 SPIN & WAIT 并消耗更多的CPU。 此外mutex也没有提供任何死锁检测和预防机制,这些都彻底取决于Kgx mutex的用户自身的行为。
Latch在 内部视图(例如X$KSLLT)中提供 全面的诊断信息。 KGX mutex在(x$mutex_sleep、x$mutex_sleep_history等内部视图)中提供部分信息, 同时也容许其用户在回调程序中用特定信息填充这些视图。
除了共享和排他模式以外, KGX mutex还提供一种examine 模式, 容许其在不以共享或排他模式持有mutex的状况下client检查一个mutex的状态以及其用户数据。 这种模式是latch所没有的
Latch 和Enqueue lock队列锁对比,如下是latch和enqueue的几个重大区别:
在典型状况下,latch被认为将仅仅被持有很短的一段时间(ms级别),而enqueue 将被持有 比之长得多的多的时间(秒=》分钟=》小时)。 例如TX 队列事务锁在整个事务的生命周期中被持有 。 latch被设计出来就是为了在 函数运行到某几十个乃至上百个指令过程当中被持有,这是很短暂的过程
latch是为了不同一时间 有一个以上的进程运行类似的代码片断, 而enqueue是为了不同一时间多于一个的进程访问相同的资源
latch的使用较为简单, 而enqueue的使用则因为命名空间namespace和死锁检测 的问题而较为麻烦
latch只有2个模式 共享和排他, 而enqueue 则支持6个模式
RAC中 latch 老是本地存放在当前实例的SGA中, 而enqueue能够是Local的 也多是Global的
9i之前latch不是FIFO的,是抢占式的; 从9i开始 大多数latch也是FIFO了; enqueue始终是FIFO的
有同窗仍不理解 latch和enqueue的区别的话, 能够这样想一下, latch 保护的SGA中的数据 对用户来讲几乎都是不可见的, 例如 cache buffer的hash bucket 对不研究内部原理的用户来讲 等于不存在这个玩样,这些东西都是比较简单的数据结构struct ,若是你是开发oracle的人 你会用几百个字节的enqueue 来保护 几个字节的一个变量吗?
而队列锁 TX是针对事务的 , TM是针对 表的,US是针对 undo segment的,这些东西在实例里已经属于比较高级的对象了,也是用户常可见的对象, 维护这些对象 须要考虑 死锁检测、 并发多模式访问、RAC全局锁 等等问题,因此须要用更复杂的enqueue lock。
死锁dead lock
为了使得latch使用足够轻量级 ,死锁预防机制十分简单。 由此Oracle开发人员 在构建一个latch时会定义 一个数字级别 level (从 0 到 16 ), 而且Oracle要求它们必须以level增序顺序获取。 若一个进程/线程在持有一个 latch的状况下,要求一个相同或者更低level的latch的话,KSL层会生成一个内部错误, 这种问题称为 “latch hierarchy violation”。
SQL> select distinct level# from v$latch order by 1; LEVEL# ---------- 0 1 2 3 4 5 6 7 8 9 10 11 14 15 16
仅有以nowait模式get latch时能够以级别(level) 非兼容的级别得到一个latch,可是这种状况很是少。
Latch Level 级别
Oracle在定义latch level的时候 取决于如下2个原则:
那些latch是在被持有的状况下, 进程/线程还会去get其余的latch?
当已经有latch被进程/线程持有的状况下, 那些latch还会被 get?
如上文dead lock的描述, latch level的一大做用是 帮助减小latch dead lock。
Latch Class
latch的类class定义了以下的内容:
spin count
yield count (number of times we yield cpu before sleeping)
wait time sample rate (0 implies it is not enabled)
sleep (in microseconds and repeated [see below])
对于post/wait 类而言 SLEEP_BUCKET和SLEEP_TIME 是被忽略的。
如下是几个latch class:
Class 0 Post/Wait Class ,绝大多数latch都是该类型
Class 1 Waiter List Latch。 该Latch保护对应latch的Waiter List,这种latch被假定老是只被持有很是短的时间(指令级别), 有充分的理由花费更多的spin count 消耗更多的CPU , 并尽量减小sleep时间
Class 2 那些因为多种缘由,不能使用post/wait机制的latch 。 例如process allocation latch 这个闩 是在一个新进程建立时所须要获取的,可是新进程还没加载post/wait的上下文,显然没法用post/wait , 因此这种 latch不能用post /wait机制
Class 3 很是短持有的latch, 特性与class 1相似。
CLASS_KSLLT字段表明了latch的类型
SQL> select CLASS_KSLLT,count(*) from x$kslltr group by CLASS_KSLLT; CLASS_KSLLT COUNT(*) ----------- ---------- 2 1 0 702 SQL> select KSLLTNAM,CLASS_KSLLT from x$kslltr where CLASS_KSLLT=2; KSLLTNAM CLASS_KSLLT ---------------------------------------------------------------- ----------- process allocation 2
从9.0.2 开始 每一个latch class的SPIN COUNT、YIELD COUNT 、WAITTIME_SAMPLING 、 SLEEP_TIME[1] …. SLEEP_TIME[i] 均在参数_latch_class_X中定义 。
SQL> col name for a20 SQL> col avalue for a20 SQL> col sdesc for a20 SELECT x.ksppinm NAME,y.ksppstvl avalue,x.KSPPDESC sdesc FROM SYS.x$ksppi x, SYS.x$ksppcv y WHERE x.inst_id = USERENV ('Instance') AND y.inst_id = USERENV ('Instance') AND x.indx = y.indx AND x.ksppinm like '%latch%class%'; NAME AVALUE SDESC -------------------- -------------------- -------------------- _latch_class_0 latch class 0 _latch_class_1 latch class 1 _latch_class_2 latch class 2 _latch_class_3 latch class 3 _latch_class_4 latch class 4 _latch_class_5 latch class 5 _latch_class_6 latch class 6 _latch_class_7 latch class 7 _latch_classes latch classes override SQL> select INDX,SPIN,YIELD,WAITTIME,SLEEP0 from X$KSLLCLASS; INDX SPIN YIELD WAITTIME SLEEP0 ---------- ---------- ---------- ---------- ---------- 0 20000 0 1 8000 1 20000 0 1 1000 2 20000 0 1 8000 3 20000 0 1 1000 4 20000 0 1 8000 5 20000 0 1 8000 6 20000 0 1 8000 7 20000 0 1 8000 8 rows selected.
举例来讲 _latch_class_1=”5000 2 0 1000 2000 4000 8000″ 则
SPIN_COUNT=5000
YIELD_COUNT=2
wait time sampling: 0 (不收集,通常都是1即收集)
增序的sleep time 1000 => 2000 => 4000 => 8000, 单位是microseconds,超过4次则保持在8000
每个wait class 适应本身对应_latch_class_X中的SPIN_COUNT、YIELD_COUNT等参数 。 而实例参数_SPIN_COUNT只作为向后兼容,若对应的latch Class没有本身的SPIN_COUNT属性才会生效。
由此实际生效的SPIN_COUNT由由如下几个参数 按优先级从高到低生效:
首先是 设置过的_latch_class_X 中的SPIN_COUNT
设置够的_SPIN_COUNT
内部函数
注意 除非是oracle support建议你去修改这些latch参数,不然在任何系统中都不应去尝试修改它们,若是你确实遇到了latch free的问题,那么你应当首先作 SQL 调优 和并发调整。
SPIN
还记得 电影《inception》盗梦空间里中旋转的陀螺吗, 旋转的陀螺 在英文里就是spinning top。 spin 自旋是 latch话题中一个频率很高的词,可是一直以来咱们对自旋的理解都不够完全 ,下面咱们完全解释 9i之后的自旋SPIN 和 Busy Latch原理。
SPIN 是指 当进程首先尝试获取latch失败后( 通常是别人持有了该latch), 有2种选择 要么是退让CPU(yield CPU) 休眠一段时间后再从新尝试获取latch , 要么是 本进程抱着但愿在CPU 上空转,由于若是我不用CPU了 让给别人用了 就会形成context switch上下文切换 (vmstat 里看到的CS),而我在CPU上空转的话就能够等等看这个latch是否会在这段时间里被人家释放, 个人一次空转称为SPIN 一次, 而SPIN_COUNT定义了我在此次总的SPIN 操做里总共SPIN 空转多少次,例如SPIN_COUNT=2000(注意 见上文中对SPIN_COUNT的描述)就是说 我有机会空转 2000次, 空转一次后 我跑去查看一下latch是否被别人释放了,若是没有我继续下一次空转, 若是是释放了 那么我就得到这个latch了,也就是SPIN_GETS成功了。若是SPIN 2000次了仍是没有等到释放latch,则SPIN_GETS没有成功, 以后该SLEEP就SLEEP( 9i先后 从9i开始有区别,具体见下文)。
若是上述SPIN GET的成功得到了latch,那么由于我没有退让CPU 也就没有上下文切换, 因此显然我得到latch的速度要比直接sleep并重试来的快。
另假设我提升了某个latch对应的 spin_count ,例如修改latch_class_1中的SPIN_COUNT为更高的值,则在上述状况下可能SPIN循环的次数更多,也就意味着有更高的几率在 SPIN阶段得到 latch, 而代价是SPIN消耗更高的 CPU时间片。 相反 若下降SPIN_COUNT,则意味着SPIN阶段得到latch的几率下降, SPIN消耗相对少的CPU。
在中古的硬件中 可能有仅有1个CPU的系统,虽然如今不多见了, 可是显然在仅有一个CPU的状况下SPIN是无心义的,由于若是你把惟一的一个CPU用来SPIN了,显然 真正持有对应latch的那个进程获取不到CPU,获取不到CPU的结果是它没法释放这个latch。在这种环境里代码自动把spin_count调整为1。
SPIN 与Latch Busy
9i以前的 spin与latch busy 运做伪代码能够点击这里(main for 8i)。
从9.0.2开始oracle 开始大量启用 post/wait和latch class机制 , 咱们来描述一下 伪代码
SLEEPs //睡眠次数计数 yields //yield 计数 copyright askmaclean.com on_wait_list = FALSE; while (若是未得到latch) { 在对应的latch上SPIN ,循环次数为SPIN_COUNT,SPIN_COUNT 来源通常为 _latch_class_X if (得到latch) break; if(yields < YIELD_COUNT) // YIELD_COUNT来源为latch_class_X { yields++; yield CPU ; } else { yield =0; if (若是latch是post/wait机制的) { on_wait_list=TRUE ; get wait list latch ; //得到wait list latch add current process to wait list; //将被进程加入到wait list的尾部 free wait list latch; //释放wait list latch wait to be posted; //等待被post } else { wait for SLEEP_TIMES[sleeps] microseconds; //等待SLEEP_TIME[sleeps]对应的时间 来源为latch_class_X if ( sleeps < SLEEP_BUCKETS) //SLEEP_BUCKETS 通常为4 sleeps++; } } } //若是某一刻得到了一个post/wait latch,且本进程在wait list上,则须要从wait list 上把本身移走: if( 若是我在wait list上) { get wait list latch ; //得到wait list latch remove current process from wait list; //从wait list 上把本身移走 free wait list latch; //释放wait list latch }
从9i开始绝大多数 latch都是 post/wait class的, 出去少许非post/wait class的latch和PMON进程外, 进程都会进入非时控的sleep (sleep[1]..[sleep[i] i到4之后再也不增长) 不会本身醒来, 仅在该latch的持有者 释放该latch 且 等待的进程在wait list的头部的状况下被post唤醒 (awake)。 Oracle 选择这种非时空的sleep的缘由 为了不在miss后引发反复的 上下文交换context switch 以便改善性能。
可是这种实现也存放一种风险, 即须要应对那些 持有latch进程意外终止 和 存在丢失 post的bug的状况。