1. 程式人生 > >Oracle後臺專家解決library cache鎖爭用的終極武器

Oracle後臺專家解決library cache鎖爭用的終極武器

今天來給大家分享一個Oracle使用中的小技巧。 當某條SQL語句或者物件被反覆訪問,過多的軟解析可能會造成大量的“library cache:mutex X”爭用,有什麼樣的方法處理此類問題呢?這是個頭疼的問題。 今天的話題,就是介紹如何利用hotcopy來緩解library cache中的熱點爭用。在oracle 11g中,“library cache:mutex X”是個有點特殊的mutex,因為它存在於幾個不同的位置,需要進一步的資訊進行分辨。這裡還會簡單描述library cache的結構,理解library cache對認識oracle是非常重要的。

解決效能問題沒有特別的方法,主要靠對資料庫整體結構的準確理解。瞭解各種等待發生的原因,以及其在整個體系中的位置,才能做出正確的判斷。 所以我會先大致介紹下10G和11G對library cache這個結構的保護方式。 這樣你就會理解這個問題發生的可能原因。 然後,我們來嘗試通過建立物件的hotcopy來緩解這類爭用。

其中個別內容是我自己摸索和猜測出來的,難免有錯誤和不準確的地方,歡迎大家指正。

也許不是所有的同學都清楚library cache的結構,先在這裡簡單描述一下。

我們知道,當編譯過的SQL已經存在於library cache中的時候,oracle會進行軟解析,重用這個結構。 那麼oracle如何知道是否已經存在可用的SQL呢? 是通過如圖中的Hash結構。所有已經編譯過的library cache object會被組織在一個hash結構中。 當進行搜尋時, Oracle會對SQL語句進行hash計算,根據結果找到相應的hash bucket,比如bucket 6, 然後再搜尋連結在這個bucket上的所有handle,這裡你可以認為每個handle對應唯一的SQL文字。

一種情況,如果“Handle X” 正好是要找的SQL,則會向內搜尋“Handle X”包含的child,尋找可用的cursor。 如果有完全符合條件的可用child,則重用,完成軟解析。 如果沒有,則建立一個新的child,完成一次硬解析。

另一種情況,如果沒有找到符合條件的handle,說明SQL 文字是新的,則需要建立一個新的handle和第一個child。 同樣完成一次硬解析。

那麼為什麼要在這個結構上進行鎖保護呢?什麼操作需要加鎖?

一個很簡單的例子,如果沒有鎖的話,假設process 1和process 2同時執行同一個SQL,當他們都沒有找到匹配的Handle時,有可能重複新增針對同一SQL的Handle Y。

同樣的,當兩個程序同時搜尋Handle X下是否有匹配的Child時,也有可能在此Handle下,新增兩個相同的Child。

因此,為了避免這種情況發生,對此結構的鎖是必要地。 由於這些操作都是快速的記憶體操作,此類鎖不宜採用複雜的結構,沒有共享模式和獨佔模式之分。 所以,只有一個程序可以對某個結構進行搜尋和修改。也就是說,即使是軟解析,在結構內進行搜尋時,也是序列的。

在10g和11g,library cache鎖的粒度是不同的。

10G的時候,保護這個結構的是 latch: library cache。 這個latch的預設數量為大於CPU Count的最小質數,而預設最小的bucket個數為509,這就意味著每個latch要保護多個Hash Buckets以及相應的handle。 也就是說,對於一組bucket和handle,每次只有一個程序可以搜尋修改。因此,這個latch經常成為熱點。

11G以後,此latch被替換為library cache: mutex X。鎖的粒度變小,每個bucket和每個handle都被一個單獨的mutex保護,大幅度減小了爭用

然而,從latch名字本身,我們區分不出等待是發生在hash bucket上,還是handle上。需要通過p1來進行分辨。

如果P1是bucket的編號,通常比較小。 如果下面查詢返回結果,說明爭用發生在hash bucket上。 返回結果為連結在此bucket上的物件。

select KGLNAHSH, substr(KGLNAOBJ, 1, 30) Name,

         KGLHDNSP Namespace, KGLNAHSV Hash

from x$kglob where KGLHDBID = &p1;

這種情況下,鎖是在bucket上,即對handle進行訪問操作時發生的。

實際上,這種情況並不常見,因為每個bucket上鍊接到的handle通常不會很多。 為了提高hash表的效率,當bucket上平均handle數量超過2時,oracle會將bucket的個數翻倍,重建hash表,用空間交換時間。 在鏈不長的情況下,一般handle上會先遇到瓶頸。

更常見的爭用位置,是在 handle上,這說明某個特定cursor或者object被搜尋時發生爭用。

如果下面查詢返回結果,則說明是這種等待。返回結果為handle所對應的物件。 可能是cursor,也可能是object

    select kglnaown, kglnaobj

      from x$kglob

     where kglnahsh = &p1;

明白了大致的原理之後,就可以想象到這個mutex爭用的可能原因。

首先,它有可能是硬解析造成的。 當發生硬解析時,無論要建立一個新的handle,還是要建立一個新的Child,都需要額外大量的時間來完成,相應地,對這個mutex的持有時間會明顯變長,造成爭用。

由於硬解析會向shared pool申請大量的記憶體,因此這種情況會伴隨一系列其他等待如 latch: shared pool, library cache load lock。 解決辦法就是分析硬解析過多,或者version count過高的問題。

還有一種情況,就是OS資源不足,特別是CPU資源緊張。 這種情況下,oracle程序無法獲取足夠的資源去完成相應的工作,無法及時釋放此mutex,造成請求的堆積。

再有,就是某個持有者長時間不釋放此mutex,造成請求的積壓。這是不正常的, 需要對mutex持有者的當前狀態進行分析。 Errorstack, process dump。 如果持有者被其他程序阻塞,則通過hanganalyze等工具繼續查詢最終持有者。

另外,個別Oracle bug也會造成此類問題。

如果,以上原因都被排除掉,那麼,對某熱點SQL或熱點object的頻繁訪問,也會造成此問題。 也就是我們之前說過的,哪怕是軟解析,也會在查詢library cache結構的時候序列。 這類問題比較讓人頭疼,一方面,SQL語句來自業務,我們不能人為減少SQL的執行次數和效率。 另一方面,如果SQL的文字相同,就一定會被hash到一個特定的handle上,這個是無法改變的。從前遇到這類問題非常讓人頭疼,一般可能的解決辦法見下一頁。

那麼接下來該怎麼辦呢?

首先,可以查下是否有連線風暴。 連線風暴可能造成某些PLSQL物件的頻繁執行,是造成此類問題的常見原因之一。

Version count如果很高,也會引發這個問題,因為找到handle以後, 對child的搜尋是用遍歷的方式,如果version很高child很多,則每次搜尋的時間很可能會變長。會造成對handle上的鎖,持有的時間也變長。所以,這也是一個可能的原因。

另外,和開發聯絡,研究SQL或某個物件如此高的併發是否正常的。 如果SQL來自不同的模組,則可以試著修改來自不同位置的SQL文字(比如加空格,或無用註釋),使他們hash到不同的handle上。

增加session_cache_cursors是個可能的嘗試,如果cursor被cache住,child的位置會被儲存在pga中,那麼查詢child的速度會明顯加快,對Mutex的持有時間也會變小。

如果以上方法均不適用,我們最後可以嘗試建立hotcopy來改善此問題。

對於資源爭用的問題,解決辦法只有兩個,要麼減小需求,要麼增加資源數量。而hotcopy的原理,實際就是在hash SQL文字的時候,加上PID的部分。 這樣,哪怕執行相同的SQL文字,不同程序就有可能會被hash到不同的handle上。 這實際增加了資源的數量。當然,copy的數量是可控的。

製作了hotcopy以後,相同的物件和child可能多次出現在library cache的不同位置,造成了一部分空間浪費,但卻可以解決library cache中熱點物件或熱點cursor的爭用。也是一種用空間換時間的辦法。

hotcopy這個功能是通過fix 9239863和fix 9282521引入的。 11.2.0.2以上版本可以直接使用。 低版本資料庫需要安裝相應地補丁。 針對11.2之前版本的資料庫,需要設定兩個隱含引數。 slide裡有詳細的描述。針對cursor 和 object對應不同的方法。

11.2之後,可以通過標準API進行設定。

http://docs.oracle.com/cd/E11882_01/appdev.112/e40758/d_shared_pool.htm#ARPLS68085

下面我們用一個簡單的實驗,檢視到底發生了什麼。 在試驗中,我模擬了一個場景,大量session反覆執行某一個SQL。

從查詢結果來看,記憶體中ASH捕捉到的等待事件, library cache: mutex X 佔據了很高的比例。

AWR報告反應了類似的內容。等待進入了top 1。

經查詢,library cache: mutex X等待的P1非常集中。

經過驗證,P1的值指向該條cursor的handle。 其實你看這個數字的長度,就知道不可能是hash bucket。 這個handle所在的bucket是14503。

接下來, 我們用之前介紹的方法來啟用基於此cursor的hotcopy。

由於我的實驗機器CPU太少,所以 我提前設定了隱含引數”_kgl_hot_object_copies”=4來指定copy的個數。

在這裡我們使用了11.2提供的API進行hotcopy的實施,針對cursor,需要完整的hash,可以從X$KGLOB.KGLNAHSV中獲取。

然後再次提交同樣的負載。

這一次,我們看到,ASH捕捉到的mutex等待開始分散到多個P1上,總數量在變少(忽略32127143,這是第一次試驗遺留的結果)。 通過進一步驗證能夠確認,每個handle的內容都是這個cursor。

這裡我也注意到14503這個hash bucket上的爭用增加,如果你還記得之前的輸出,這其實是修改前那個cursor所在的hash bucket。 我猜測即使做了hotcopy,請求仍舊會經由這個bucket跳轉。由於handle上的請求處理速度變快了, bucket上的爭用開始顯現,從library cache的結構來看,這些是合理的。

從library cache dump 中驗證,的確增加額外4個handle,相同的物件名。

在新生成的AWR報告中, library cache: mutex X 等待時間下降了50%, 從1882秒,下降到942秒.  由此可以驗證,針對純粹的library cache的熱點爭用,hotcopy是可以進行緩解的。

最後做一個簡單地小結。

首先,從library cache的結構看, 即使是軟解析,對child和handle的搜尋,也是序列的。 雖然這個操作速度很快,但仍會造成熱點爭用。

另外,造成library cache: mutex X爭用的原因很多,只有純粹的熱點爭用才需要用hotcopy來解決。

最後,以我的經驗來看,解決資料庫效能問題,沒有固定的套路。 Oracle是個極其龐大複雜的體系,幾乎不可能通過幾個簡單的等待事件推測出根本原因,不會有這樣的“神”。 正確的分析方法是,儘可能多的收集證據,然後利用自己的知識和對整體結構的理解,推匯出一個“故事”把所有證據串聯起來,有了“故事”以後,再收集更多的證據,反過來來驗證故事的正確性。