全面解析9i以后的Oracle中的Latch闩锁

Latch闩锁在Oracle中属于 KSL Kernel Services Latching, 而从顶层视图来说 KSL又属于VOS  Virtual Operating System。

maclean-latch

Latches 是一种 低级别(low-level)的 锁机制, 初学IT的同学请注意 低级不代表简单, C语言对比java语言要 低级一些但C并不比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.com

在Oracle中Test-And-Set类型的latch使用原生的Test-And-Set指令。 在绝大多数平台上, 零值zero代表latch是 空闲或者可用的 , 而一个非零值代表 latch 正忙或者被持有。  但是仅在HP PA-RISC上 正相反。  TAS latch只有2种状态 : 空闲 或者 忙。

2) Compare-And-Swap 简称 CAS

Compare-And-Swap 也是计算机专有名词, Compare-And-Swap(CAS)是一个用在多线程环境中实现同步的 原子指令( atomic )。 该指令将在一个给定值(given value)和 指定内存位置的内容 之间比对,仅在一致的情况下 修改该内存位置的内容为一个 给定的 新值(不是前面那个值)。  这些行为都包含在一个 单独的原子操作中。 原子性保证了该新的值是基于最新的信息计算获得的; 如果该 内存位置的内容在同时被其他线程修改过,则本次写入失败。 该操作的结果必须说明其到底是否执行了 取代动作。 它要么返回一个 布尔类型的反馈, 要么返回从 指定内存地址读取到的值(而不是要写入的值)。

关于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个场景中生效:

  1. 当某个latch被持有,但是持有进程遇到了某一个错误                                      ==》主动
  2. 当持有latch的进程die掉,需要PMON进程前去恢复这个latch的状态        ==》被动

经典情况下, 执行清理函数的进程要么把正在执行过程中的操作回滚掉 ,要么前滚掉。 为了为 前滚(rolling forward)或者回滚(rolling back)提供必要的信息, oracle在latch结构中加入了recovery的结构,其中包含了这个正在执行过程中的操作的日志信息, 这些日志信息必须包含足以前滚或者回滚的数据。  如我们以前讲过的, 理论上oracle进程可能在运行任何指令的时候意外终止,所以清理恢复是常事。

清理恢复最恶心的bug是PMON 执行cleanup function时因为 代码bug ,把PMON自己给弄dead了, 由于PMON的关键的后台进程,所以这会引起实例终止。

Latch和 10.2.0.3后引入的KGX Mutex对比

和 latch一样, kgx mutex也是用来控制串行化访问SGA中数据的 ,但仍有一些重要区别:

  1. KGX mutex要比  CAS latch更轻量级, mutex 结构大约为16个字节, 而一个latch结构大约是100个字节。 因此 mutex嵌入到大量其他对象结构中是可行的, 因为他的struct 足够小
  2. 之所以mutex可以提供 更小的结构 很廉价的成本,其原因是 使用mutex有一个简单的前提假设: 对于mutex的争用是很小的。 因此没有为mutex那样提供一个优化过的wait  list , mutex做更多的 SPIN & WAIT 并消耗更多的CPU。   此外mutex也没有提供任何死锁检测和预防机制,这些都完全取决于Kgx mutex的用户自身的行为。
  3. Latch在 内部视图(例如X$KSLLT)中提供 全面的诊断信息。 KGX mutex在(x$mutex_sleep、x$mutex_sleep_history等内部视图)中提供部分信息, 同时也允许其用户在回调程序中用特定信息填充这些视图。
  4. 除了共享和排他模式之外, KGX mutex还提供一种examine 模式, 允许其在不以共享或排他模式持有mutex的情况下client检查一个mutex的状态以及其用户数据。 这种模式是latch所没有的

Latch 和Enqueue lock队列锁对比,以下是latch和enqueue的几个重大区别:

  1. 在典型情况下,latch被认为将仅仅被持有很短的一段时间(ms级别),而enqueue 将被持有 比之长得多的多的时间(秒=》分钟=》小时)。 例如TX 队列事务锁在整个事务的生命周期中被持有 。 latch被设计出来就是为了在 函数运行到某几十个乃至上百个指令过程中被持有,这是很短暂的过程
  2. latch是为了避免同一时间 有一个以上的进程运行相似的代码片段, 而enqueue是为了避免同一时间多于一个的进程访问相同的资源
  3. latch的使用较为简单, 而enqueue的使用则由于命名空间namespace和死锁检测 的问题而较为麻烦
  4. latch只有2个模式 共享和排他,  而enqueue 则支持6个模式
  5. RAC中 latch 总是本地存放在当前实例的SGA中, 而enqueue可以是Local的 也可能是Global的
  6.  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。

SPIN 

inception-top

还记得 电影《inception》盗梦空间里中旋转的陀螺吗, 旋转的陀螺 在英文里就是spinning top。 spin 自旋是 latch话题中一个频率很高的词,但是一直以来我们对自旋的理解都不够彻底 ,下面我们彻底解释 9i以后的自旋原理。

Comments:

Post a Comment:
  • HTML Syntax: NOT allowed
About

author's avatar The Maclean Liu
Advanced Customer Services
AskMaclean Logo 10g_ocm SHOUG

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today