Oracle Cache Buffer Chains

eric0435發表於2019-03-26

一個Oracle Buffer是一個Oracle段物件快取塊。一個Oracle buffer一開始時包含與Oracle塊中相同的資訊。一個buffer的內容依賴於段型別以及它是滯是一個段頭塊。buffer有許多種狀態透過v$bh的state列來表示,它們可能被歸納成在種模式:free(可用),dirty(髒)與pinned(固定)。

Free Buffers
當一個buffer與磁碟上的資料塊匹配時它的狀態就是free。一個free buffer可以看作是一個映象buffer,因為它映象了磁碟上的資料塊。下面的查詢簡單的顯示瞭如何判斷buffer cache中free buffers的數量。一個free buffer可能確實是空的(例如,在例項重啟之後),但它將最有可能包含真實的塊資訊,比如行記錄。一個free buffer可以被替換而不會產生任何損壞,因為有一個副本儲存在磁碟上。當然,如果一個事務提交,那麼至少被修改的buffer必須被記錄到聯機重做日誌檔案中。

SQL> select count(*) from v$bh where status='free';
  COUNT(*)
----------
        24

一個free buffer可能不是被頻繁的訪問。也許一個查詢需要訪問單行資料因此需要將資料塊放入buffer cache中,而這個buffer之後再也沒有被訪問過。而另一方面,一個free buffer也可以是被頻繁訪問的。例如,如是一個特定的資料塊被重複地查詢,它將被頻繁的訪問,但它的狀態仍然是free狀態,因為buffer沒有被改變過。如果你對freebuffer的定義簡單又清晰,那麼許多Oracle的演算法將也變得清晰,這將使理解,檢測與解決競爭更容易。

Dirty Buffers
當一個buffer它不能與磁碟上的相關塊進行匹配時它的狀態就是dirty。對一個buffer進行的任何改變都會使用它的狀態變為dirty,因為buffer將不再與磁碟上的塊相匹配。當記憶體中的改變還沒有被寫入磁碟而要對其進行覆蓋時,dirty塊是不能被替換的。一旦資料庫寫程式將一個dirty buffer寫入磁碟,那麼buffer將與磁碟上的塊再一次匹配那麼這個buffer的狀態將變為free。

一個dirty buffer可能也不被頻繁訪問。假設一行記錄被修改但其它程式不需要訪問這個buffer。因為行記錄被改變這個塊確實是dirty的,但它不被頻繁訪問。當然,也有被頻繁訪問的dirty buffers。簡單地重複更新一行記錄將確保它的buffer的狀態為dirty又被頻繁的訪問。

下面的查詢顯示dirty buffers的狀態可能是xcur或write。將在cache buffer chains中詳細介紹current與consistent模式的buffers。xcur狀態意味著一個程式已經改變了一個current模式的buffer的狀態為這種狀態,並且程式可能現在更新buffer中的行記錄,雖然行記錄現在仍然受制於其它條件,比如行級鎖。排他模式不會阻止多個使用者改變相同buffer中的多行記錄,它簡單表示當current模式的buffer可以被改變。在RAC環境中這是至關重要的,可能有多個共享current模式buffers(scur),但在整個RAC資料庫中每個塊只有一個排他current模式buffer存在。

SQL> select status, count(*) from v$bh where dirty='Y' group by status;
STATUS       COUNT(*)
---------- ----------
xcur            20792
scur              919
pi               2567

Pinned Buffers
當一個buffer被pinned時,它不能被替換。另一種看待pinning的方式是對buffer的一種非官方鎖。因為一個buffer不是一種關係結構,標準的鎖機制不能應用。Pinning一個特定的buffer,latches或mutexes可以控制訪問整組buffers。Pinning可以與latch與lock一起連用來確保適當的序列化,保護與並行控制被實現。

假設一個伺服器程式將要讀取一個buffer中的一行記錄。當你仍然在訪問這一行記錄時,有人使用其它的buffer替換了你正在訪問的buffer這是極端粗魯的。這就像你正在讀一本書時,有一個人說"讓我看看",並從你手中搶走一樣。許多程式可以pin相同的buffer(讀取相同的塊),但是隻有一個程式能pinned這個buffer,它不能被替換。當一個free buffer的行記錄正被查詢時,它的狀態從free變為pinned再次回到free。當free buffer中的行記錄被修改後,它的buffer狀態將從free變為pinned,再變為dirty。

Oracle沒有透過v$bh檢視來顯示pinned buffers,但任何被touched的buffer也就是被pinned了。當一個buffer正被移動到寫列表中並且正更新touch計數時Oracle將也會pin這個buffer。

Buffer Headers的作用
當buffers內建在buffer cache中並且buffers確實已經被改變了,列表管理實際作用於buffer headers,而不是實際的buffers。一個buffer header是一個最佳化過的記憶體結構它包含關於一個buffer和與它相關的塊資訊,但不包含塊資料比如行記錄。

為什麼對於buffer cache沒有檢視v$bc?,這是因為一個buffer與一個塊的後設資料被儲存在buffer header,並且它的後設資料對於我們的效能分析是需要的。因此檢視被命名為v$bh,對於buffer header有三個關鍵的列表或鏈:
.Cache buffers chains(CBCs)被用來快速判斷一個Oracle塊是否內建在buffer cache中。

.最近最少使用(LRU)列被用來在cache中保留住被頻繁訪問的buffers並找到free buffers。

.寫列表包含不久將被寫入磁碟的dirty buffers。

重要的是理解buffer headers的這三個列表而不是實際的buffers。單個buffer header總是內建在一個CBC中和一個LRU鏈或一個寫列表中。

三個列表的維護是在buffer header級別,不是buffer級別,更不是在資料塊級別。我們許多人被教導當buffer內建在buffer cache中時,buffers它們本身是被連結的。這是不正確。每個buffer都與一個buffer header相關聯,並且在各種列表中操作的是buffer header。

Cache Buffer Chains
簡而言之,CBCs被用來回答“這個buffer是否在buffer cache中,如果在,它在哪裡”這本質上是一個搜尋型別的問題。很多型別的搜尋演算法可能被用來獲得答案:二叉樹,B+樹,B*樹,順序搜尋,雜湊演算法,或一些演算法組合。Oracle選擇使用一種雜湊演算法,緊接著使用快速順序搜尋。

雜湊演算法
雜湊演算法可以非常快速,因為整個結構通常被儲存在記憶體中並且要求一個單獨的數學計算,同時存在一些記憶體訪問來回答搜尋問題。雜湊結構有許多變化,但所有的雜湊結構都是由一個雜湊函式,雜湊桶與雜湊鏈組成的。

雜湊函式
雜湊函式接收輸入並使用定義的範圍來產生一個輸出。輸入被叫作一個雜湊值。x mod 10函式可以簡單地被用來確保不管輸入的正數雜湊值,它的輸出總是在0到9之間。雜湊值輸入11,輸出將是1。一個好的雜湊函式將會產生均勻分佈的輸出。當Oracle將要搜尋一個buffer時,基於資料塊的檔案號與塊號的組合(它也叫資料塊地址DBA)來生成一個雜湊值。因此雜湊函式本質上是對buffer的資料塊檔案號和塊號進行雜湊運算。這是一種非常方便並可以快速雜湊運算的情況。

雜湊桶
雜湊輸入值將被雜湊到桶,每個輸出值代表了一個單獨的桶。在許多雜湊情況下,可能輸入的雜湊值的數量超過桶數。對於Oracle來說,可能的雜湊輸出值就是Oracle資料塊的數量。但在任何情況下,雜湊輸入值的數量將與buffercache中的buffers的數量相等。

當有兩個雜湊值被雜湊到相同的桶時,這叫作碰撞。碰撞對於雜湊來說是很常見的。碰撞可以透過增加雜湊桶的數量來最小化,但可能對於高效能程式來說是一種災難,比如Oracle資料庫。例如,假設x mod 10的雜湊函式有1000雜湊輸入值,這將肯定會出現碰撞。為了避免碰撞,雜湊演算法輸出完全均勻的輸出將需要1000個雜湊桶。使用一種極好的雜湊演算法與大量的雜湊桶兩種方法減少碰撞。如果雜湊演算法不變,那麼可以增加雜湊桶的數量。

雜湊鏈
每個雜湊桶都有一個相關聯的雜湊鏈。當一個搜尋的物件被雜湊到一個桶時,這個桶的鏈被順序搜尋來查詢物件。如果物件在雜湊鏈中沒有找到,我們知道物件不在整個雜湊結構中。如果雜湊鏈很短,順序搜尋將很快完成。如是物件不在cache中,鏈長度最好為零。

Oracle的CBC結構是一種複雜的記憶體結構,並且Oracle必須要維持序列化控制。所以它使用了一種序列化結構:latch或mutex。

如何破壞CBC的效能
要學習如何解決效能問題的最好方法就是知道如何模擬問題,有三種典型的方法來降低CBC的效能:
.當減少latches的數量時,剩餘latches的併發將會增加
.如果減少CBCs的數量,平均每個CBC的長度將會增加,剩餘chains的併發與CBC的掃描時間也會增加
.如果buffer克隆變得激烈,那麼頻繁訪問的chain將變得很長,會增加併發與CBC的掃描時間

減少latches來限制併發
使用單個latch,序列化將被保證,但是併發性將受到嚴重的限制。當另一個程式請求latch時而它被其它程式所持有時就會產生競爭。在這個例子中,簡單地增加一個latch可以解決這個問題。如果存在上百成千個程式需要訪問CBCs,那麼可以看到存在嚴重的併發效能限制問題。幸運地是預設情況下Oracle建立了上百個CBC latches。

Oracle知道它的雜湊函式不完美並且將會產生碰撞。一種減少碰撞的方法是有大量的CBCs。但你第一反應會覺得更多的CBCs將會消耗更多的記憶體,但事實不是這樣的。每個buffer header必須內建在一個CBC鏈上,與CBC鏈的數量及長度無關。當使用更多的CBC鏈時,而buffer headers的數量不變時,平均CBC鏈的長度會減小。因此,對於每個CBC鏈雖然有一些額外的記憶體消耗,但真正的記憶體消耗者是buffer headers的數量,不僅僅是CBC鏈的數量。

許多年以前規則定義latches的數量不應該超過CPU核數的兩倍。很明顯Oracle已經修改了規則,CBC latches只是Oracle資料庫中許多latches中的一種。

Oracle可能處理多個CBC latches,有人會認為對於每個CBC將有一個latch,但Oracle認為這是不必要的且一個latch可以管理上百個CBC鏈。

如果CBC鏈比buffers多,這意味著有一些CBC鏈將不會關聯buffer header,這將有效的使CBC鏈的長度變為零。

[oracle@jytest2 ~]$ sqlplus / as sysdba
SQL*Plus: Release 12.2.0.1.0 Production on Thu Mar 21 10:28:02 2019
Copyright (c) 1982, 2016, Oracle.  All rights reserved.
Connected to:
Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
SQL> col param format a50 heading "Instance Param and Value" word_wrapped
SQL> col description format a20 heading "Description" word_wrapped
SQL> col dflt format a5 heading "Dflt?" word_wrapped
SQL> select rpad(i.ksppinm, 35) || ' = ' || v.ksppstvl param,
  2  i.ksppdesc description,
  3  v.ksppstdf dflt
  4  from x$ksppi i,
  5  x$ksppcv v
  6  where v.indx = i.indx
  7  and v.inst_id = i.inst_id
  8  and i.ksppinm in
  9  ('db_block_buffers','_db_block_buffers','db_block_size',
 10  '_db_block_hash_buckets','_db_block_hash_latches'
 11  )
 12  order by i.ksppinm
 13  /
Instance Param and Value                           Description          Dflt?
-------------------------------------------------- -------------------- -----
_db_block_buffers                   = 97136        Number of database   TRUE
                                                   blocks cached in
                                                   memory: hidden
                                                   parameter
_db_block_hash_buckets              = 262144       Number of database   TRUE
                                                   block hash buckets
_db_block_hash_latches              = 8192         Number of database   TRUE
                                                   block hash latches
db_block_buffers                    = 0            Number of database   TRUE
                                                   blocks cached in
                                                   memory
db_block_size                       = 8192         Size of database     FALSE
                                                   block in bytes

引起CBC latch競爭的最好和最簡單的方法之一就是建立一個大的buffer cache來快取更多的塊,然後將CBC latches的數量減少到一個。Oracle從10g開始就不允許CBC latches的數量小於1024,但是即使有1024個CBC latches和足夠的邏輯IO能力,也能經常看到CBC latch競爭。

透過減少CBC的數量來增加CBC的掃描時間
如果CBCs很長,那麼掃描它的時間將會引起顯著的競爭。另外其它程式獲得CBC latch的時間也會顯著增強。一種很明顯的方式是增加平均每個CBC的長度來減少CBC的數量,這可以透過減少雜湊桶的數量來完成。簡單地將例項引數_db_block_hash_buckets減少到50,確保你查詢的塊內建在buffer cache中,那麼會很快得到CBC latch競爭。因為Oracle至少要確保64個雜湊桶來忽略你的設定,但這仍然會有大量的競爭。

在現實中,一種解決CBC latch競爭的方法是增加雜湊桶的數量,這將減少平均每個CBC的長度。如果一個特定的CBC很長且被頻繁文章,那麼這個解決方案將不能提高效能。此外Oracle建立了大量的CBC,因此增加雜湊桶的數量不像增加CBC一樣能顯著的提高效能,但它有一種有效的方法應該值得考慮。

使用克隆Buffers來增加CBC的掃描時間
雖然長CBC的問題很少見,但如果出現了,那麼情況是很嚴重的。理解這是如何發生的不僅僅可以幫助你解決這個問題還能更深入的理解CBCs,latch,undo與讀一致性。它涉及RAC系統。

長CBC代表了一個非常有挑戰性的問題。首先,雜湊結構是很快速的因為幾乎沒有掃描,因此長CBC會迅速降低使用雜湊演算法的好處。第二,一個掃描程式必須處理一個CBC latch,不是隨便一個CBC latch,這個CBC latch保護特定的CBC。一個長CBC意味著CBC latch將被持有更長時間並且當掃描列表將使用更多的CPU。另外,因為CBC latch被持有的時間更長,這將增加另外的程式競爭latch的可能性。當競爭latch的程式在spinning與在sleeping時釋出等待事件時都是要消耗CPU的。但問題遠不止如此。

正常情況下,Oracle的雜湊演算法使用的CBC的數量是buffers的兩倍還多,因此CBC的長度很短。長CBC出現的唯一方式是多個buffers被雜湊到相同的CBC上。通常這不是一個問題,但也可能出現。為了解析這種情況,先了解塊克隆與雜湊。當一個Oracle塊被cached後,只有單個當前模式buffer能被修改。如果buffer中的一行需要被修改,單個當前模式buffer必須是可用的。當前模式buffers有時也叫CU buffers。在RAC系統中,如果需要的當前模式buffer內建在另一個例項中,那它必須被髮送到你使用的這個例項中然後才可以修改buffer。

假設一個伺服器程式在時間T100正執行一個查詢。這個程式訪問資料字典並知道它將必須訪問一個特定塊,因此它將被雜湊到合適的CBC,獲取合適的CBC latch,掃描CBC,並找到當前模式buffer的buffer header。然而在檢查buffer header時,發現當前模式buffer在時間T200被修改過,是在伺服器程式開始執行查詢之後。這意味著在查詢執行後需要的行記錄已經被修改過了。 Oracle的預設讀一致性模式要求被返回的資訊與查詢開始執行時的一致。因此Oracle必須採取操作來確保被返回的資訊對於時間T100來說是正確的。

Oracle現在要麼找到一個buffer的副本,要麼構建一個當前模式buffer的副本,因此這個buffer代表了時間T100所處處的情況。一個buffer副本通常叫做buffer克隆。克隆一個buffer是一種相對昂貴的處理。首先,必須找到一個free buffer,然後buffer header必須被合適的連線到CBC結構與LRU鏈結構。

理解潛在的重大效能影響的關鍵是理解被克隆的buffer的buffer header將內建在CBC結構中的什麼位置。因為被克隆的buffer是一個合法的buffer,它在buffer cache中佔據了空間,能被共享且必須被定位。這意味著它必須被合適的內建在CBC結構中。被克隆的buffer的檔案號與塊號與它的當前模式buffer的相同,這意味著它必須被雜湊到相同的CBC。因此,如果一個buffer有50個克隆副本,與它相關的CBC將至少有50個buffer header那麼長,並且如果與其它buffer出現碰撞可能更長。Oracle對此無能為力,因為哈演算法是基於檔案號與塊號的。

不僅free buffer搜尋演算法有利於替換克隆的buffer,但Oracle試圖限制每個buffer的克隆數量。Oracle想要每個buffer的克隆數量不超過隱含引數_db_block_max_cr_dba,它的預設值為6。然而如果克隆變得很激烈,一個buffer的克隆副本很容易超過6個。

SQL> col name for a30
SQL> col value for a20
SQL> col describ for a50
SQL> select x.ksppinm NAME,y.ksppstvl value,x.ksppdesc describ
  2  from x$ksppi x, x$ksppcv y
  3  where x.inst_id=USERENV('Instance')
  4  and y.inst_id=USERENV('Instance')
  5  and x.indx=y.indx
  6  and x.ksppinm like '%&par%';
Enter value for par: _db_block_max_cr_dba
NAME                           VALUE                DESCRIB
------------------------------ -------------------- --------------------------------------------------
_db_block_max_cr_dba           6                    Maximum Allowed Number of CR buffers per dba
1 row selected.

有許多克隆的buffer不一定意味著有效能問題。如果真的出現效能問題,CBC latch競爭問題將非常明顯。如果出現這種情況並發現克隆buffer的問題,那麼考慮以下可能的補救措施:
.修復應用程式
這通常是必須要做的。這是非常痛苦的,需要開會,如果應用程式開發者參與將會非常專業化,並且通常要求應用程式以某些方式被修改來減少單個克隆buffer被頻繁的訪問。

.移動行記錄
如果幸運的話,可能存在多行記錄使得buffer被頻繁訪問。如果可能散這些行,因此多個buffer現在不再被頻繁的訪問。當修改傳統的pct_free與pct_used儲存引數是一種選擇時,為了增加控制,可以考慮設定一個塊可以儲存的最大記錄數。意外地是這不僅僅是簡單地執行類似於alter table all_status minimizer records_per_block 5語句

.平衡工作負載
如果能控制工作負載強度,在克隆活動高峰期間,考慮減少與buffer克隆活動相關的工作負載。雖然這不是一個令人興奮的解決方案,工作負載平衡也能對效能產生積極影響。

CBC競爭識別與解決方案
一些解決方案可以幫助你解決CBC競爭的問題。在嘗試解決CBC latch問題之前,確保它們存在。

SQL> @swpctx
Remember: This report must be run twice so both the initial and
final values are available. If no output, press ENTER twice.
DB/Inst: RLZY/RLZY1                                               25-Mar 11:24am
Report:   swpctx.sql           OSM by OraPub, Inc.                Page         1
            System Event CHANGE (17 sec interval) Activity By PERCENT
                                       Time Waited  % Time    Avg Time     Wait
Wait Event Display Name                      (sec)  Waited Waited (ms) Count(k)
-------------------------------------- ----------- ------- ----------- --------
latch: cache buffers chains                 10.610   96.28        15.7        1
control file parallel write                  0.160    1.45         7.6        0
log file parallel write                      0.030    0.27        15.0        0
log file sync                                0.000    0.00         0.0        0

如果資料庫系統是Oracle 10g之前的版本,那麼top wait event將會是latch free,就需要確認latch問題是CBClatch。對於Oracle 10g及以後的版本,wait event將是latch: cache buffers chains。在大多數情況下,CPU子系統將被大量利用並且負擔過重。以下是可能的CBC latch解決方案:
.最佳化邏輯IO SQL語句
當回答“buffer是否在buffer cache”中時CBC結構將變得緊張起來,期待的答案總是“Yes”,如果答案為“No”,將會看到順序讀或分散讀等待事件。因此從應用程式角度來看,查詢執行活動主要是buffer gets也就是邏輯IO的SQL盡你所能地減少邏輯IO消耗。這是典型的SQL最佳化,包括索引,以及在效能問題出現時減少執行速率。

.增加CPU處理能力
在大多數情況下,CPU子系統將被過多利用並且可能是作業系統瓶頸。latch的獲得與相關的記憶體管理可能消耗過多的CPU資源。做任何可以減少CPU消耗與能增加CPU能力的事。查詢在高峰期間沒有執行或正在執行的程式。考慮增加或者使用更快的CPU。如果正在執行在虛擬環境中,考慮確保Oracle系統已經增加CPU資源。然而,請注意除非應用程式工作負載已經顯著增加,增加的CPU處理能力通常將被快速地消耗掉。真正的解決方案可能是其它的方案。增加CPU能力可能是一個快速解決方案,但它可能不能真正地解決問題。

.檢查buffer克隆問題
無論何進遇到CBC latch競爭問題,都需要檢查是否存在buffer克隆的問題。這是很少見的情況,但如果遇到了,那麼解決方案與其它解決方案是非常不同的。

.增加CBC latch數量
這通常會帶來一些安慰,但不是真正的最佳化邏輯IO SQL。隱含引數_db_block_hash_latches控制著CBC latch的數量

.增加CBC buckets
它很難對效能產生影響,因為Oracle預設情況下,建立了大量的buckets。除非之前減少了CBC buckets的數量,增加這個引數的大小將會顯著地影響效能。


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

相關文章