latch是一種輕量級用於保護oracle共用記憶體結構,使用者並行作業一致性的序列化鎖定機制,如SGA中,各種資料被反覆從磁碟讀取到記憶體,又被重新寫回到磁碟上,如果有並發使用者做相同的事情,oracle必須使用一種機制來保證資料在讀取的時候,只能由一個會話來完成,這就是latch,latch 不會造成阻塞,是只會等待,與每個latch相聯絡的還有一個清楚過程,當持有latch的進程成為死進程時,系統清除過程就會被調用,系統lock導致 使用者等待,需要考慮系統的邏輯設計是否有問題,如多使用者對主鍵的刪除或者修改,是否有使用者使用select… for update這樣的文法,外鍵是否建立索引。latch 爭用多半要考慮系統及資料庫自身設計問題,如綁定變數,熱塊及參數設定是否合理。
spin:比如資料緩衝中的某個塊要被讀取,我們會獲得這個塊的 latch,這個過程叫做spin,另外一個進程恰好要修改這個塊,他也要spin這個塊,此時他必須等待,當前一個進程釋放latch後才能spin 住,然後修改,如果多個進程同時請求的話,他們之間將出現競爭,沒有一個入隊機制,
一旦前面進程釋放所定,後面的進程就蜂擁而上,沒有先來後到的概念,並且這一切都發生的非常快,因為Latch的特點是快而短暫。
spin與休眠:
休眠意味著暫時的放棄CPU,進行環境切換(context switch),這樣CPU要儲存當前進程運行時的一些狀態資訊,比如堆棧,訊號量等資料結構,然後引入後續進程的狀態資訊,處理完後再切換回原來的進程 狀態,這個過程如果頻繁的發生在一個高事務,高並發進程的處理系統裡面,將是個很昂貴的資源消耗,所以Oracle選擇了spin,讓進程繼續佔有 CPU,運行一些空指令,之後繼續請求,繼續spin,直到達到_spin_count值,這時會放棄CPU,進行短暫的休眠,再繼續剛才的動作。
oracle中,latch是一種輕量級的鎖。一般來說,latch由三種記憶體元素成:pid(進程id),記憶體位址和記憶體長度。Latch保證對共用數 據結構的排它性訪問,以此來保證記憶體結構的完整性不受到損壞。在多個會話同時修改或者檢視(inspect)sga中同一個記憶體結構時,必須序列化訪問以 保證sga中資料結構的完整性。
進程擷取latch有兩種模式:willing-to-wait和No_wait。no-wait模式只在少數latch中使用。通過no-wait模式 擷取latch的統計資訊記錄在immediate_gets immediate_misses列中,這些列在v$latch,v$latch_parent,v$latch_children視圖中都存在。一般來 說,no-wait模式在第一次擷取一些有很多子latch的latch比如redo copy時使用。如果一個進程第一次擷取這些子latch中的任何一個失敗,它會立即使用no-wait模式詢問下一個。只有當採用no-wait模式試 圖擷取所有的子latch都失敗以後,才會轉而採用willing-to-wait模式。
通過willing-to-wait模式擷取latch的統計資訊存放在gets和misses列中。每當一個進程用willing-to-wait模式去擷取一個latch時,gets都會增加。
如果進程在第一次請求latch時,latch可用,就會直接獲得該latch。在修改任何受到保護的資料結構之前,進程會將一些恢複資訊寫入到latch恢複區,這樣當獲得latch的進程發生異常時,pmon進程才能夠清理該進程持有的latch。
如果請求latch時,該latch不可用,進程就會在cpu中等待一小段時間(spin)然後重新請求latch。如果latch一直不可用,該過程 (spin一段時間然後重新請求)會一直重複。重複的次數由隱含參數_spin_count決定,預設值2000。如果在請_spin_count次之內 獲得了latch,就對spin_gets和misses列各加一,否則,進程v$session_wait中記錄latch free等待事件,然後釋放cpu,轉入睡眠狀態。睡眠一定時間後,進程被喚醒並重複上面的過程,一直到獲得latch。在成功獲得latch後,才會更 行sleep列得統計資訊。
由於進程只有在獲得latch後才會停止對latch得請求,如果某個持有latch的進程發生異常,其他請求該latch的進程該怎麼辦?豈不是要一直 等待下去?不會的。當一個進程請求latch失敗一定次數後,它會請求pmon進程查看該latch的持有人,如果持有進程異常,pmon就會清理該進 程,釋放latch。
每個latch都有一個從0到13的優先順序編號。父latch和獨立latch的優先順序編號是在oracle核心代碼中固定的。子latch是÷在執行個體啟動時建立,其優先順序編號從其父latch繼承。使用優先順序可以避免死結。
當一個進程請求no-wait模式的latch時,該latch的優先順序編號必須和它當前已經持有的latch的優先順序編號相同。
當一個進程請求willing-to-wait模式的latch時,該latch的優先順序編號必須比它當前已經持有的latch的優先順序編號要大。
進程擷取Latch的過程:
任何時候,只有一個進程可以訪問記憶體中的某一個資料區塊,如果進程因為別的進程正佔用塊而無法獲得Latch時,他會對CPU進行一次spin(旋轉),時 間非常的短暫,spin過後繼續擷取,不成功仍然spin,直到 spin次數到達閥值限制(這個由隱含參數_spin_count指定),此時進程會停止spin,進行短期的休眠,休眠過後會繼續剛才的動作,直到擷取 塊上的Latch為止。進程休眠的時間也是存在演算法的,他會隨著spin次數而遞增,以厘秒為單位,休眠的閥值限制由隱含參數 _max_exponential_sleep控制,預設是2秒,如果當前進程已經佔用了別的Latch,則他的休眠時間不會太長(過長會引起別的進程的 Latch等待),此時的休眠最大時間有隱含參數_max_sleep_holding_latch決定,預設是4厘秒。這種時間限制的休眠又稱為短期等 待。另外一種情況是長期等待鎖存器(Latch Wait Posting),此時等待進程請求Latch不成功,進入休眠,他會向鎖存器等待鏈表(Latch Wait List)壓入一條訊號,表示擷取Latch的請求,當佔用進程釋放Latch時會檢查Latch Wait List,向請求的進程傳遞一個訊號,啟用休眠的進程。Latch Wait List是在SGA區維護的一個進程列表,他也需要Latch來保證其正常運行,預設情況下share pool latch和library cache latch是採用這個機制。
如果將隱含參數_latch_wait_posting設定為2,則所有Latch都採用這種等待方式,使用這種方式能夠比較精確的喚醒某個等待的進程, 但維護Latch Wait List需要系統資源,並且對Latch Wait List上Latch的競爭也可能出現瓶頸。
資料緩衝池Latch爭用
訪問頻率非常高的資料區塊被稱為熱快(Hot Block),當很多使用者一起去訪問某幾個資料區塊時,就會導致一些Latch爭用,最常見的latch爭用有:
(1) buffer busy waits
(2) cache buffer chain
Cache buffer chian產生原因:
當一個會話需要去訪問一個記憶體塊時,它首先要去一個像鏈表一樣的結構中去搜尋這個資料區塊是否在記憶體中,當會話訪問這個鏈表的時候需要獲得一個Latch,如果擷取失敗,將會產生Latch cache buffer chain 等待,導致這個等待的原因是訪問相同的資料區塊的會話太多或者這個列表太長(如果讀到記憶體中的資料太多,需要管理資料區塊的hash列表就會很長,這樣會話掃描列表的時間就會增加,持有chache buffer chain latch的時間就會變長,其他會話獲得這個Latch的機會就會降低,等待就會增加)。
Buffer busy waits 產生原因:
當一個會話需要訪問一個資料區塊,而這個資料區塊正在被另一個使用者從磁碟讀取到記憶體中或者這個資料區塊正在被另一個會話修改時,當前的會話就需要等待,就會產生一個buffer busy waits等待。
產生這些Latch爭用的直接原因是太多的會話去訪問相同的資料區塊導致熱快問題,造成熱快的原因可能是資料庫設定導致或者重複執行的SQL 頻繁訪問一些相同的資料區塊導致。
查看造成LATCH BUFFER CACHE CHAINS等待事件的熱快
select distinct a.owner, a.segment_name
from dba_extents a,
(select dbarfil, dbablk
from x$bh
where hladdr in (select addr
from (select addr
from v$latch_children
order by sleeps desc)
where rownum<20)) b
where a.relative_fno=b.dbarfil
and a.block_id<=b.dbablk
and a.block_id+a.blocks>b.dbablk;
查詢當前資料庫最繁忙的Buffer,TCH(Touch)表示訪問次數越高,熱點快競爭問題就存在
select *
from (select addr,
ts#,
file#,
dbarfil,
dbablk,
tch
from x$bh
order by tch desc)
where rownum<11;
查詢當前資料庫最繁忙的Buffer,結合dba_extents查詢得到這些熱點Buffer來自哪些對象
select e.owner, e.segment_name, e.segment_type
from dba_extents e,
(select *
from (select addr, ts#, file#, dbarfil, dbablk, tch
from x$bh
order by tch desc)
where rownum<11) b
where e.relative_fno=b.dbarfil
and e.block_id<=b.dbablk
and e.block_id+e.blocks>b.dbablk;
如果在Top 5中發現latch free熱點塊事件時,可以從V$latch_children中查詢具體的子Latch資訊
select *
from (select addr, child#, gets, misses, sleeps, immediate_gets igets,
immediate_misses imiss, spin_gets sgets
from v$latch_children
where name= ‘cache buffers chains’
order by sleeps desc)
where rownum<11;
擷取當前持有最熱點資料區塊的Latch和buffer資訊
select b.addr, a.ts#, a.dbarfil, a.dbablk, a.tch, b.gets, b.misses, b.sleeps
from (select *
from (select addr, ts#, file#, dbarfil, dbablk, tch, hladdr
from x$bh
order by tch desc)
where rownum<11) a,
(select addr, gets, misses, sleeps
from v$latch_children
where name= ‘cache buffers chains’) b
where a.hladdr = b.addr;
利用前面的SQL可以找到這些熱點Buffer的對象資訊
select distinct e.owner, e.segment_name, e.segment_type
from dba_extents e,
(select *
from (select addr, ts#, file#, dbarfil, dbablk, tch
from x$bh
order by tch desc)
where rownum<11) b
where e.relative_fno = b.dbarfil
and e.block_id<=b.dbablk
and e.block_id+e.blocks>b.dbablk;
結合SQL視圖可以找到操作這些對象的相關SQL,然後通過最佳化SQL減少資料的訪問,或者最佳化某些容易引起爭用的操作(如connect by等操作)來減少熱點塊競爭
break on hash_value skip 1
select /*+ rule */ hash_value,sql_text
from v$sqltext
where (hash_value, address) in (
select a.hash_value, a.address
from v$sqltext a,
(select distinct a.owner, a.segment_name, a.segment_type
from dba_extents a,
(select dbarfil, dbablk
from (select dbarfil, dbablk
from x$bh
order by tch desc)
where rownum<11) b
where a.relative_fno = b.dbarfil
and a.block_id <= b.dbablk
and a.block_id + a.blocks > b.dbablk) b
where a.sql_text like ‘%’ || b.segment_name || ‘%’
and b.segment_type = ‘TABLE’)
order by hash_value, address, piece;