LATCHES, LOCKS, PINS AND MUTEXES

LATCHES

如果内存不足(即没有足够大的单块空闲内存),不足以在library cache中创建新对象,Oracle 会使用 LRU(最近最少使用)算法将对象从它们的哈希链表中delink——这意味着选择两个当前相互指向的现有对象,将新对象中的前向和后向指针设置为指向这两个现有对象,然后修改其中一个对象的后向指针和另一个对象的前向指针,以指向新对象。

  Hash_buckets               Hash_chains
       H1             S1 <-> S2 <> S5 <> S9
                      ^                 ^
                      |______________________|
       H2             S3 <>  S8
                      ^       ^
                      |_______|
       H3             S4  <>  S6
                      ^        ^
                      |_________|
       H4             S7

例如,如果用户发出一个语句:S10,其哈希值与bucket:H2 相同,则需要采取以下步骤:

  • 将 S10 的前向指针设置为 S8
  • 将 S10 的后向指针设置为 S3
  • 将 S3 的前向指针设置为 S10
  • 将 S8 的后向指针设置为 S10
    因此最终hash bucket H2 如下所示:
        H2             S3  <>  S10 <> S8
                       ^             ^
                       |_______________|

考虑一种场景。User1发出语句S1,发现它属于哈希桶H1。同时,另一位用户User2需要加载另一个对象,因此需要将桶 H1 中的对象 S2 淘汰(age out)出缓存。用户2将需要从H1桶中解除链接S2,为此他将:
– 将 S1 的前向指针设置为指向 S5
– 将 S5 的后向指针设置为指向 S1
– 设置 S2 的前向/后向指针,以便将其连接到内存空闲列表中free list
因此最终哈希桶 H1 如下所示:

H1             S1 <> S5 <> S9
               ^           ^
               |______________|

并且 S2 被链接到内存空闲列表的双向链表中。
如果允许两个用户同时访问哈希桶 H1 的内容,User1 会扫描双向链接的哈希链,可能会跟随指向 User2 刚刚丢弃的对象(S2)的指针,因为 User2 还没有来得及修正 S2 的两个指针。Oracle 必须确保在一个用户遍历链表时,另一个用户不能修改链表,反之亦然。
这就是 Oracle 闩锁(latches)发挥作用的地方。闩锁是简单的、低级的序列化机制,用于保护系统全局区(SGA)中的共享数据结构。闩锁消除了允许多个进程同时修改共享内存时会出现的问题。服务器或后台进程在操作或查看这些结构之一时,会在非常短的时间内获取一个闩锁。

共享池闩锁用于保护和实现共享池中的并发性。多个哈希桶由一个闩锁保护,因此每当进程想要在哈希桶中搜索对象时,它必须首先获取保护该桶的闩锁,遍历链表,找到对象后执行需要的操作,然后释放闩锁。如果另一个用户想要遍历同一个闩锁保护的哈希链,他将等待直到第一个用户释放闩锁。这就导致了闩锁争用。
#闩锁争用– LOCKS AND PINS

  • Latch contention 的产生是因为闩锁被持有的时间过长,或者因为对闩锁的需求过高,或者两者兼而有之。
    然后,有三个关键方面与闩锁争用有关:
  • 覆盖library cache的不同闩锁数量
    如果闩锁数量较多,则较少的哈希桶将由一个闩锁保护,因此你不太可能与需要遍历由你想要的闩锁保护的链表的其他用户发生冲突;另一方面,闩锁数量越多,你可能需要在某种形式的维护、报告或垃圾收集中进行更多的工作。
    在共享池架构中,由一定数量的library cache闩锁保护的哈希桶(根据需要增长到下一级)的数量是固定的
    在 10g 之前,覆盖library cache的闩锁数量非常少。该数量取决于 CPU 的数量(大致与 cpu_count 参数相同),最多为 67 个闩锁。实际上,这是一个非常少的数量,考虑到即使在执行频率很低的语句上也存在冲突的可能性;两个进程不必执行相同的 SQL,甚至不必访问相同的哈希桶,也可能在同一个闩锁上发生冲突 - 它们只需访问由同一个闩锁保护的两个哈希桶。
  • 每次需要获取给定闩锁的次数
    需要获取闩锁并遍历链表的次数越多,与需要相同闩锁的其他用户发生冲突的可能性就越大。
    我们可以通过在找到对象后将 KGL 锁附加到对象上来最小化搜索library cache中对象的次数,这样我们就可以有一个快捷方式到达它(存储在 PGA 中作为open/closed游标)。下次发出相同的语句时,我们将在 PGA 中找到它,不需要再次搜索对象的哈希链(软解析)。
  • 持有闩锁的时间
    如果每个人持有闩锁的时间越长,他们与其他人持有他们想要获取的闩锁的问题就越大。
    我们应该尽量避免然和长时间的持有闩锁。这意味着当我们找到了正在搜索的内存位时,如果我们需要做一些耗时的事情,我们可以pin对象以保护该内存,同时使用它,以便我们可以释放闩锁。

因此,在服务器进程找到相应的哈希桶后,遵循的步骤是:
1.获取相应哈希桶的闩锁。
2. 在对象上获取锁,以便将指向对象的指针放置在 PGA 中(作为open cursor)。
3. pin对象并释放闩锁。
4.对对象执行任何想要的操作(例如执行语句/存取过程)。
5.再次获取闩锁,unpin对象并释放闩锁。
lock和pin通常是在共享模式下,除非正在进行修改。

如何获得library cache lock(KGL 锁)?

有三种主要方法可以获得 KGL 锁:

  • 您可以编写前端代码来“持有”您知道您会频繁使用的游标。
  • 您可以设置 session_cached_cursors 参数,这样 Oracle library代码会在看到您使用一个语句超过两三次时自动开始持有一个游标。
  • 您可以从 PL/SQL 半自动保持游标的方式中获益,这些游标是在 PL/SQL 调用中(显式或隐式地)打开的——从 Oracle 9.2.0.5 开始,设置 session_cached_cursors 参数也会控制此功能。session_cached_cursors 参数决定在代码未显式保持游标时可以保持的游标数量。它还控制由会话运行的 PL/SQL 代码打开的游标可以保持的数量。

KGL 锁的优点

除了最小化我们在library cache中搜索对象的次数之外,锁还提供其他额外的好处:

  • 通过锁,一个客户端可以阻止其他客户端访问相同的对象。library cache lock使用对象句柄作为资源结构,并对该资源进行锁定。如果资源不可用或不兼容,会话必须等待library cache对象变为可用。
  • library cache lock,也称为解析锁,是为了维护对象及其依赖对象之间的依赖机制而必需的。例如,如果需要修改一个对象(例如表)的定义,那么依赖对象(例如引用该表的 SQL 语句的执行计划)必须无效化。这种依赖关系是通过library cache lock来维护的。例如,如果从一个表中删除了一个列,那么所有依赖该表的 SQL 语句必须在下次访问该对象时无效化并重新解析。library cache lock的设计旨在实现此跟踪机制。如果我们对一个 SQL 语句加锁,那么我们就有指向其执行计划的指针存储在 PGA 中。如果底层表的结构被修改,那么该语句上的解析锁将被打破,并且 PGA 中的游标将被无效化,即下次重新执行相同的语句时,它将在library cache中被搜索并且随后被硬解析。

为什么需要library cache pin (KGL pin)

KGL pin在实际使用对象时起作用。虽然 KGL lock会将一个对象保留在内存中(PGA 中的游标指向共享池中的对象),但对象的某些部分是可以动态重新创建的(例如 SQL 语句的执行计划),即使已经设置了 KGL lock,如果对内存的需求很大,这些部分仍然可能被丢弃(如果共享池需要内存,PGA 中游标指向的内存位置可能会被其他对象占用)。
然而,当您实际使用对象时(例如运行 SQL 语句),您需要确保这些可重新创建的部分不会被从内存中移出,因此您需要pin住对象以保护它们。
pin一个对象会将heap加载到内存中。如果客户端想要修改或检查对象,他必须在获得lock后获得一个pin。例如,当会话正在执行或访问一个依赖对象(如 SQL)时,它的底层对象不应该被修改。如果用户试图修改底层对象(例如表结构),他将需要在表上以独占模式获取library cache pin。如果依赖的 SQL 正在执行,pin将不可用,并且会发生library cache pin的等待。因此,在library cache对象上的解析锁可以被破坏之前,必须以独占模式获取library cache pin,然后才能更改library cache对象。通常,这发生在长时间运行的 SQL 语句中。

library cache locks和pin状态被外化在三个 x$ 表中显示,即 x$kgllk、x$kglpn 和 x$kglob。

  • x$kgllk 用于显示对象上的所有lock结构。
  • x$kglob 中的条目作为资源结构。
  • x$kglpn 用于显示所有library cache pin的状态。

KGL lock和pin的问题

library cache中的lock和pin住机制导致了两个问题的出现。
KGL lock和 KGL pin本身只是一些小的内存包,它们曾经是通过调用从共享池中分配内存而按需单独创建和释放的。由于一个 KGL lock大约占用 200 字节,而一个 KGL pin大约占用 40 字节,可以想象,在它们之间,内存的不断分配和释放可能会导致共享池中的空闲内存最终变成一种“蜂窝”状态——即,总体上有很多空闲内存,但没有许多大的连续内存块。KGL pin特别麻烦,因为它们会非常快地出现和消失;KGL lock则不太糟糕,因为它们可以在一个对象上保持一段时间。
KGL lock和 KGL pin的另一个问题是,要使用它们,必须不断创建一个内存包,正确地标记它,并插入到一个链表中(或者相反,从链表中移除一个链接并将其放回共享池中),而且你必须在独占地持有一个latch时进行这些操作。因此,对于非常繁忙的系统来说,整个lock/pin问题可能会成为一个显著的可扩展性威胁。
在 10g 中,Oracle 公司引入了library cache lock 闩锁和library cache pin 闩锁,这允许在同一个library cache latch覆盖的不同哈希桶上进行一些并发操作(例如,你可以在一个桶中pin住一个游标,而我在另一个桶中lock一个游标,因为我们不需要同时持有同一个library cache latch。
然而,随着从 10g 逐步过渡到 11g,整个 KGL lock/KGL pin 机制逐渐被mutex机制所取代。

MUTEXES

为了改善游标执行和硬解析,Oracle 在 10gR2 中引入了一种新的细粒度内存序列化机制。在某些与共享游标相关的操作中,Mutexes被用来替代library cache latch和library cache pin。基本上,mutex的工作方式与闩锁类似,但操作mutex的代码路径更短,因此更加轻量化,通常直接由硬件支持。因此,使用mutex更快,消耗更少的 CPU,并且相比于闩锁机制大大提高了并发性。
在 32 位的 Linux 安装中,常规闩锁结构为 110 字节,而mutex仅为 28 字节。此外,mutex需要的指令更少。获取一个闩锁需要 150-200 条指令,而获取一个mutex只需要大约 30-35 条指令。

mutex结构更小的原因在于它们提供的关于谁在等待什么以及等待多长时间的信息较少。你可以获得关于睡眠时间的信息,但无法获得请求次数和未命中次数的信息。
#互斥锁的好处:
它们可以以共享模式或独占模式获取,并且获取mutex可以采用wait mode或no-wait模式。
1.mutexes具有较少的false contention(伪争用)。以前,多个哈希桶由同一个闩锁保护。因此,两个用户在搜索由同一个闩锁保护的不同桶时,容易发生不必要的false contention,即争用的是保护机制(闩锁),而不是你试图访问的目标对象。与闩锁不同,使用mutex可以为每个受保护的结构创建一个mutex。这意味着false contention(伪争用)的可能性大大降低,因为每个结构都可以由其自己的mutex保护。现在,每个父游标和子游标都有自己的mutex,我们不必争用由许多游标共用的闩锁。
2.替代闩锁和 pin:Mutex 具有双重性质;它们既可以作为序列化机制(类似于闩锁),也可以作为 pin(例如,防止对象老化)。而闩锁不能被多个会话同时获取,Mutex 则可以被多个会话引用,前提是所有会话以共享模式引用该 Mutex。以共享(S)模式引用某个 Mutex 的会话总数称为reference count(ref count,引用计数)。Mutex 的引用计数存储在 Mutex 本身中。Mutex 也可以以独占(X)模式由一个会话持有。
Mutex 的引用计数取代了library cache pin。在使用闩锁的情况下,每当一个会话执行一个语句时,它首先创建,然后删除(释放)一个library cache pin,但使用 mutex 时,每个会话递增和递减ref count(因此ref count取代了多个独立的 pin)。这意味着在ref count降为零之前,即没有用户当前访问该对象时,该对象不会被age out(老化)移出去。
3.Mutex 结构位于每个子游标句柄中,并且 Mutex 本身充当游标 pin 结构。以前要更改游标 pin 状态,我们需要获取library cache latch,但现在我们可以直接修改游标的 Mutex refcount。
根据 Oracle 专家 Tanel Podar 的说法:主要的可扩展性优势在于每个子游标句柄中都有一个 Mutex 结构,并且 Mutex 本身充当游标 pin 结构。因此,如果你有一个打开的游标(或在session cursor cache中缓存的游标),你不需要获取library cache latch(以前在更改游标 pin 状态时需要获取),而是可以直接修改游标的 Mutex 引用计数(借助于会话 UGA 中打开游标状态区域的指针)。
因此,在pin/unpin游标时,你具有更高的可扩展性(不需要library cache latch,几乎没有false contention(伪争用)),并且不需要分配/维护单独的 pin 结构。

其他:
1.对于解析等操作,仍然需要library cache latch,而mutex仅解决了library cache中pin的问题。
目前,mutext仅用于library cache游标(而不是其他对象,如 PL/SQL 存储过程、表定义等)。由于mutex是一种通用机制(而不是特定于library cache的),它们也用于 V$SQLSTATS 底层结构。当启用mutex时,你将不再看到来自 X$KGLPIN 的cursor pin(因为 X$KGLPN 是基于 KGL pin 数组的固定表 - 这种表不再用于游标)。

2.请注意,latch和mutex是独立的机制,即一个进程可以同时持有闩锁和互斥体。在 Oracle 10.2.0.2+ 中,当_kks_use_mutex_pin为 true 时,library cache pin闩锁的使用被mutex替代,同时像 V$SQLSTATS 数组和父游标检查等其他一些内容也受到mutex的保护。然而(使用 kksfbc() 进行正确的子游标查找仍然受到library cache latch的保护),这可能会在频繁的软解析与cursor cache过少以及库缓存哈希链过长的情况下成为问题(请记住,即使是普通的哈希链扫描,library cache latch也总是被独占地获取)。在 10g 中,可以看到使用了 3 种类型的mutex:

  • Cursor Stat
  • Cursor Parent
  • Cursor Pin
    因此,在 11.2 版本中,Oracle 放弃了所有的小内存块和链表结构 - 实际上,它甚至放弃了用于保护哈希链的闩锁,并将它们全部替换为mutex。每个哈希桶都由一个mutex进行保护。如果哈希桶上有mutex,那么每个桶就只有一个微型闩锁,而不是最多 67 个闩锁覆盖 131,072 个桶。如果对于每个library cache对象都有一个mutex来代表 KGL 锁,并且另一个来代表 KGL pin,那么就不必进行所有的内存分配和释放,并且不必运行代码来连接链表。因此,从 11g 开始,每个library cache桶都由一个单独的mutex进行保护(是的,所有的 131,072 个桶!)。

3.在 11g 中,还有一些额外的mutex,其中最重要的是library cache mutex。
4.在 11g 中,所有与library cache 相关的闩锁(除了“library cache load lock”)都被移除,相应的操作由mutexes保护。library cache”latch被“library ”mutex取代。
10g 中存在的下列latch在 11g 中不再可见:
library cache pin allocation
library cache lock allocation
library cache hash chains
library cache lock
library cache
library cache pin
5.11g 中唯一存在的library cache latch是:
library cache load lock
6.以下与library cache相关的等待事件在 10g 中存在,但在 11g 中不再出现:
latch: library cache
latch: library cache lock
latch: library cache pin
7.11g中存在的与library cache相关的等待事件有:
library cache pin
library cache lock
library cache load lock
library cache: mutex X
library cache: mutex S
OSD IPC library
library cache revalidation
library cache shutdown
当然,library cache mutex仍然不能解决所有问题(特别是与过度硬解析相关的问题!),例如,仍然有可能发生两个完全不同游标的哈希冲突。此外,如果在一个父游标下有许多子游标,并且应用程序的游标管理不当(例如,每次执行后都关闭游标,并且没有进行会话游标缓存),那么由于持续的库缓存哈希链遍历,你仍然可能在互斥锁上发生争用。
library cache lock/pin的演示可以在这里看到。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值