資源供給:併發性控制和mutex之一

yuntui發表於2016-11-03
簡單案例描述: 某商業銀行業務系統表現出在高峰期CPU消耗很高,有些消耗CPU特別高的程式,但總是會變化,業務系統響應還基本可以接受。在CPU Time表現中Parse CPU相對比較高,Hard Parse不高,系統偶爾出現cursor pin: S,v$mutex_sleep_history表現出較高的gets和sleeps。經過綜合判斷為mutex的機制衝突引起,應用patch: 6904068之後CPU消耗快速降低,當然效能是不會增加的,只是釋放了CPU資源。

     mutex的實現,是Oracle效能最佳化者的幸運,也是效能最佳化者的不幸。幸運在於除了Oracle bug之外,mutex很少會出現問題。不幸當然也在於此,當出現問題的時候,往往只能去查詢是否bug,很可悲呀。

     library cache pin 和cursor pin with mutex。
    Oracle在11g之前是主要把pin cursor相關的library cache pin轉換成了mutex,所以我們在這裡也主要講pin cursor。
    從效能最佳化的角度出發,library cache pin和cursor pin with mutex兩者沒有很大區別,mutex比較library cache pin提供了更加清晰的視角。
  
      library cache pin的實現應該不同於普通的latch,採用某種類似於佇列的形式實現。library cache pin不同於其他的latch,他會針對每個cursor建立pin,也就是可能幾十萬甚至幾百萬的pin,顯然採用普通latch實現成本太高。我們不 管他如何實現,只要知道其基於某種佇列就可以了。cursor mutex相對比較library cache pin要高階,他採用shared latch的類似應用計數實現,也就是 CAS原子操作。
    pin cursor在什麼時候發生?   
  pin cursor for create cursor
  pin cursor for execute cursor   
   可能還會有其他場景會發生pin cursor,但應該主要就是基於上面兩種情況。

  為什麼需要pin,pin的主要目的是為了防止被交換出記憶體,同時提供並行訪問控制。

   我們來看看:
   Pin cursor for Execute Cursor:
       這個時候,我們準備執行該SQL語句(Cursor),需要把Cursor Pin(Shared)在Buffer中(child cursor),在Pin Buffer的過程中,我們需要完成以下操作:
(1)、繫結變數填充
(2)、cursor依賴物件Pin以防止被(Shared)

  顯然Pin cursor可能的衝突主要來自於以下兩個方面:
  (1)、依賴的object被交換出記憶體,導致pin的成本很高
  (2)、依賴的物件被ddl操作以至於無法獲得PIN
   我一直不明白Oracle為什麼允許在Parse過程中的物件被允許交換到記憶體中,Parse之後按照道理是需要馬上執行引用,不應該被犧牲掉。
 
     在相對記憶體充足的系統中,如果不發生手工清理的flush pool,一般很少會發生以來的object被交換,因為我們剛剛在parse過程中把所依賴的物件都裝載進來。至於ddl操作則需要在依賴物件上獲得 Exclusive Lock and Exclusive PIN,並且導致依賴的cursor無效。從理論上講,cursor pin的程式在被ddl中斷之後應該重新執行parse過程,但在實踐中的發現基本表現為等待library cache pin的程式永久等待,不會進行釋放。
    從cursor pin的角度而言,我們只要做到避免在高峰期執行ddl以及相對充足的shared pool,一般來說可以避免library cache pin或者mutex cursor衝突。

    mutex:
    cursor: pin s wait on x
     cursor: pin s      
    cursor: pin x

   以上三個衝突主要在pin cursor的階段。
   cursor: pin s wait on x為正常的cursor執行pin,一般為所依賴的物件不再記憶體中導致成本很高導致,等待pin依賴物件。
              當然ddl操作也是cursor: pin s wait on x的主要原因。
   cursor: pin s為正常的執行,在極高併發的時候會發生,主要原因在於mutex採用CAS機制,在引用計數變更的時候不允許其他session pin cursor,這個時候的等待表示為cursor: pin s。對於cursor: pin s如果不是Oracle bug的話,一般只能透過應用程式層面來解決,避免cursor熱點,這個和以前處理高併發的library cache pin是一致的。
             事實上,大家可以發現相當多的cursor: pin s發生在select * from dual之類的語句上,很多應用對於dual的引用極為頻繁,從而導致其產生pin cursor衝突。一般的處理方式為改寫語句:比如select * from dual為cursor pin最嚴重的語句,則改寫為以下方式:
             select * from dual D1;
             select * from dual D2;
             等等,使其成為不同的cursor,自然就不會出現在cursor: pin s層面上的衝突了。
     cursor: pin x,這個層面應該很少見,一般只有在cursor在pin過程中被交換到出記憶體,需要從記憶體中再次載入回來才會出現。當然某些需要對於cursor進行清 理的操作也需要獲得cursor: pin x,比如flush shared pool,pugre cursor等等。

     繫結變數的填充發生在另一個層面上,一般為cursor: mutex x,一般很少會發生在繫結變數層面的衝突。
     cursor: mutex x
     cursor: mutex s

     應該極少會看到這個層面的衝突。
     如果有衝突發生,主要在兩個層面可能發生:
     hard parse在生成cursor和children cursor的時候需要cursor: mutex x,雖然大量的文章認為這個時候會在依賴物件上獲得exclusive lock and exclusive pin,但個人並不傾向於在hard parse的需要的在依賴物件施加exclusive pin,個人傾向於施加的是shared pin,除非是不再記憶體中。有興趣的童鞋可以驗證一下。
    hard parse僅僅在cursor被重用的時候才有可能會發生cursor: mutex x,所以個人認為hard parse和cursor: mutex x具有一定關係,但並不是絕對的。

     另一個可能會產生cursor: mutex x的場景應該是Create New children,也就是高版本場景。高版本需要不斷的更新LCO的children table部分,也就是cursor部分,需要獲得mutex x,大量高版本自然會引起響應的等待。
     cursor: mutex s 主要對於parent cursor進行引用檢視的時候發生,什麼時候會對parent cursor做exclusive,似乎只有age in and age out了。

     綜合上面而言,對於mutex和library cache pin的最佳化措施事實上是很簡單的:
    (1)、足夠的shared pool
    (2)、控制DDL
    (3)、控制高版本
    (4)、對於高併發的SQL,從語句級別進行人工人點劃分。
     當然,考慮Oracle bug也是一個最主要的思考方向。
    

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30633755/viewspace-2127743/,如需轉載,請註明出處,否則將追究法律責任。

相關文章