Buffer Busy Waits是怎麼產生的?

wei-xh發表於2018-08-25

哪些場景會產生Buffer Busy Waits

Buffer Busy Waits是Oracle 資料庫常見的一個等待,特別是在併發寫比較頻繁的環境裡。作為一個Oracle DBA,如果你從未遇到過Buffer Busy Waits等待,那麼你算不上一個真正的Oracle DBA。

產生這個等待的原因是,在記憶體級別,在同一時刻對同一記憶體塊進行讀寫產生了爭用。這裡我以2個程式對資料塊的操作為例,歸納出產生Buffer Busy Waits的場景:

  1. 寫寫,例如一個程式正在對記憶體塊做更改,此時另一個程式也要修改這個記憶體塊

  2. 寫讀,例如一個程式正在對記憶體塊做更改,另一個程式此時想要讀這個記憶體塊。你可能會問,oracle為什麼不去copy正在修改的塊構造CR塊,然後去讀構造出的CR塊,理由很簡單,記憶體塊正在寫(變化)的過程中去做copy,是一個不安全的動作。

按照字元的排列組合,還剩”讀讀“、”讀寫“兩個場景,這兩個場景都不會導致產生Buffer Busy Waits(這裡很重要的一個假設是2個程式),理由我們後面再給出,這裡按住不表。

為什麼要設計這個等待

Oracle為什麼要設計這個等待,這是個好問題,本篇文章也主要是為了解答這個疑問。

“為什麼”要比“是這樣做的”的更重要,前者講的是邏輯和洞見,後者描述的是過程和結果。

回答這個問題之前,首先設計一個場景,“假如沒有這個等待,讀寫資料塊是如何進行的”,當然這個場景是設計出來的,其實並不存在。

那我們開始,假設場景是這樣子的,我們要對一個資料塊做寫入操作,遵循如下步驟:

  1. 首先依據資料塊地址計算出(Hash演算法)該塊所在的Hash Bucket。

  2. 根據桶的編號,計算出保護這個桶的CBC Latch,然後申請CBC Latch,查詢資料塊在不在桶裡,這裡假設在記憶體裡。

  3. 修改資料塊。

  4. 釋放CBC Latch。

以上的描述看似是非常通暢,但是存在一個問題,由於CBC Latch的持有是排他的,在持有CBC Latch的情況下,去修改資料塊,那麼這個Latch的持有時間就會比較長,這個長是相對於Latch的獲取和釋放這種CPU原子操作的,因此在持有CBC Latch的情況下修改資料塊,對於讀寫頻繁的資料庫/塊(熱點塊),那麼勢必會造成CBC Latch的爭用,基於Latch的特性(自旋、休眠、再自旋),會造成大量CPU資源的浪費,導致資料庫的效能低下。

為了解決這個問題,Oracle設計了Buffer Pin的功能。也就是說,Buffer Pin這個機制存在的價值,是為了降低CBC Latch的爭用,節省CPU資源。

引入Buffer Pin後,修改資料塊的大致步驟如下:

  1. 首先依據資料塊地址計算出(Hash演算法)該塊所在的Hash Bucket。

  2. 然後申請CBC Latch,查詢、定位到資料塊

  3. 以X模式獲取資料塊的Buffer Pin。

  4. 釋放CBC Latch

  5. 在Pin的保護下,修改資料塊,完成後繼續以下步驟

  6. 獲得CBC Latch

  7. 釋放Buffer Pin

  8. 釋放CBC Latch

步驟複雜了許多,CBC Latch獲取/釋放共發生了兩次,工作量似乎整整大了兩倍。

可是極大程度降低了CBC latch的爭用。 因為持有CBC Latch的時間變得極短。 持有CBC Latch,只是為了在buffer header上增加buffer pin,目的變得單純和簡單。

你可能會說,Oracle這種解決方案就是按了葫蘆起了瓢,它只不過是轉移了競爭,以前是CBC Latch的爭用,現在是Buffer Pin的爭用。

這句話並沒錯。

但是Buffer Pin的爭用是不消耗CPU資源的。類似於佇列鎖的通知機制。而不會像Latch一樣去做自旋。

讀取資料塊增加S模式的Buffer Pin,修改資料塊增加X模式的Buffer Pin。S和S模式的Pin是相容的,可以併發的讀取,X和S模式是不相容的,後來的讀取會話需要產生等待。

文章的開頭,我們舉了2個程式操作同一記憶體塊的例子,這裡我們在有了一些知識之後,再做下總結和回顧。

  • 寫讀,同一個時刻,如果一個程式以X模式持有了資料塊的Buffer Pin,另一個程式想以S模式持有,那麼就會出現爭用,因為道理很簡單,X模式的Buffer Pin和S模式的Buffer Pin不相容。

  • 寫寫,同理,兩個同時欲修改同一個資料塊的程式,只有一個程式可以獲取X模式Buffer Pin,另一個會話產生Buffer Busy Waits等待。

我們平時經常說讀不阻塞寫,寫不阻塞讀,那是在物理的資料塊級別,在記憶體裡,在同一時刻對於同一個記憶體塊的寫讀/寫寫都是互相阻塞的。

這裡接著上面把場景補充完整,“讀讀”不會產生Buffer Busy Waits等待,因為Pin模式是相容的。“讀寫”不會產生Buffer Busy Waits等待,一個程式正在讀資料塊,此時另一個程式要去寫這個資料塊,寫程式很聰明,它會copy出一個current塊出來(之前的塊成為CR塊),然後在current塊上進行寫操作,透過這種方式避免了競爭,規避了Buffer Busy Waits的爭用。

因為前一個程式是在讀取,而不是修改。這個場景下的copy就是安全的。

另外一點,因為讀取資料庫塊需要在Buffer header上增加S模式的Buffer Pin,屬於記憶體的修改操作,因此即使是讀取操作,CBC Latch的持有也是排他的,但是這個增加Pin的時間極短,在壓力正常的資料庫環境中引起CBC Latch大量爭用的可能性不大。

當然如果是大量程式對同一記憶體塊頻繁讀取,就會引起CBC Latch的爭用。

一些細節

最後,有必要對一些細節再做些補充:

  • 一旦程式Pin住了一個資料塊,不需要立即去UNPin(移除Pin)。ORACLE認為在本次呼叫後還有可能繼續去訪問這個資料塊,因此直到本次呼叫結束才會做UNPin操作。

  • Oracle在對唯一索引/undo塊/唯一索引的回表/索引root、branch塊的設計上,在訪問(讀取)的時候,獲取的是共享的CBC Latch,不需要去對Buffer加Pin,直接在持有共享CBC Latch的情況下讀取資料塊。這樣做的原因是這些型別的塊,發生修改的可能性比較小,因此Oracle單獨的採用這種機制。因此對於普通資料塊的讀取都是需要獲取2次CBC Latch,而對於這種特殊的資料塊,只獲取一次共享CBC Latch就可以了。

  • 我們上面所說的情況都是在資料塊已經存在在記憶體裡的情況。如果資料塊不在記憶體,有可能會產生Read By Other Session爭用等待。

  • 上面描述只符合10G後的版本。在10G前讀讀也會產生Buffer Busy Waits,10G後把這方面的Buffer Busy Waits歸到了Read By Other Session等待裡。

總結

本文講述了Buffer Busy Waits是如何產生的及其機制,但是並沒有講解如何對其進行調優。Buffer Busy Waits等待設計的本質是為了降低CBC Latch的爭用。以兩個程式操作記憶體塊為例,在“寫寫”、“寫讀”場景下會產生Buffer Busy Waits,在“讀讀”場景下不會產生Buffer Busy Waits等待,在“讀/寫”場景下,發生寫操作的伺服器程式會去Copy出一個current塊來繼續寫操作,而不會去等待Buffer Busy Waits。


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

相關文章