全面解析9i以后Oracle Latch闩锁原理
Latch闩锁在Oracle中属于KSLKernelServicesLatching,而从顶层视图来说KSL又属于VOSVirtualOperatingSystem。Latches是一种低级别(low-level)的锁机制,初学IT的
Latch闩锁在Oracle中属于 KSL Kernel Services Latching, 而从顶层视图来说 KSL又属于VOS Virtual Operating System。
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的伪代码例子:
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的更多信息可以参考
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会包含以下 信息:
子闩 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种:
经典情况下, 执行清理函数的进程要么把正在执行过程中的操作回滚掉 ,要么前滚掉。 为了为 前滚(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中数据的 ,但仍有一些重要区别:
Latch 和Enqueue lock队列锁对比,以下是latch和enqueue的几个重大区别:
有同学仍不理解 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”。
仅有以nowait模式get latch时可以以级别(level) 非兼容的级别获得一个latch,但是这种情况非常少。
Latch Level 级别
Oracle在定义latch level的时候 取决于以下2个原则:
对于post/wait 类而言 SLEEP_BUCKET和SLEEP_TIME 是被忽略的。
以下是几个latch class:
Class 0 Post/Wait Class ,绝大多数latch都是该类型