全面解析9i之後Oracle Latch閂鎖原理

Latch閂鎖在Oracle中屬於 KSL Kernel Services Latching, 而從頂層視圖來講 KSL又屬於VOS  Virtual Operating System。html

maclean-latch

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個場景中生效:

  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.2後引入的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。


死鎖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-top

還記得 電影《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的狀況。

相關文章
相關標籤/搜索