轉載地址:http://www.itpub.net/thread-1801066-1-1.html
最近看到論壇裡好幾篇文章在討論buffer busy waits,在這裡談談我的看法。
先說說這個等待怎麼來的。
buffer busy waits 的由來。
當n個進程想以不相容的模式持有記憶體塊上的buffer pin的時候,就會產生buffer busy waits等待。
什嗎?
記憶體塊上有buffer pin ?
不是說記憶體塊的鎖都是靠latch實現的嗎?什麼時候還冒出一個buffer pin?從來沒聽說過!!
好,既然你這麼問,那我們可以先假設沒有buffer pin這東東,看看怎麼去訪問/修改一個資料區塊。(下面的過程儘可能的我做了簡化)
1)依據資料區塊的地址計算出資料區塊所在的bucket
2)獲得保護這個bucket的cbc latch
3)在這個鏈表上找尋我們需要的資料區塊
4)讀取/修改資料區塊的內容
5)釋放cbc latch
我們知道latch的獲得和釋放時間一般都是極短的(cpu的原子操作),上面5個步驟裡1,2,3,5的時間我們都可以認為是極快的操作。
但是步驟四消耗的時間相對於這幾個就大了去了。我粗糙的畫了一個圖,可以參展一下。
這樣就導致了一個問題,在大並發環境下,由於cbc latch的持有時間過長,會導致大量的latch爭用,latch的大量爭用非常容易導致系統的cpu資源出現瓶頸。需要特別說明的是,即使你所有的操作都是查詢非修改,也會導致大量的cbc latch爭用:cbc latch的持有到cbc latch的釋放這段時間過長了。
如何解決這個問題呢,說一下ORACLE的做法。
ORACLE通過讓每次訪問buffer block的會話擷取兩次cbc latch,再配合在記憶體塊上加buffer pin來解決這個問題。
看如下的步驟。
1)依據資料區塊的地址計算出資料區塊所在的bucket
2)獲得保護這個bucket的cbc latch
3)在這個鏈表上找尋我們需要的資料區塊,找到後,pin這個buffer(讀取s,修改x)
4)釋放cbc latch
5)讀取/修改資料區塊的內容
6)擷取cbc latch
7)unpin這個buffer
8)釋放cbc latch
通過這種實現方式,我們看到cbc latch的持有時間大大降低了,因為cbc latch的持有,只做了很少的事情,這樣就大大降低了cbc latch的爭用。
你可能會挑戰說,雖然cbc latch的爭用會大大減輕,可是ORACLE只不過是轉移了競爭點,現在變成了buffer lock之間的競爭。
你說的對,但是也不對!!
如果你的資料庫裡讀極多,寫極少,由於各個讀之間的buffer pin是相容的,都是s模式,因此不會產生任何的爭用。
如果你的資料庫裡寫極多,讀極小,就會產生buffer busy waits等待,但是這種等待的代價比cbc latch的等待代價要小的多,latch的spin機制是非常耗cpu的,而bufferpin的管理本質上類似於enq 鎖的機制,沒有spin機制,不需要自旋耗費大量的cpu。
如果你的資料庫是讀寫混合的情境,那麼寫會阻塞讀,產生buffer busy waits,但是讀不會阻塞寫,不會產生這個等待。這個我們後面會重點討論。
我們可以來簡單的看一下,產生buffer busy waits的幾個情境。如下代碼找到了在同一個塊上的兩條記錄。
createtable wxh_tbd as select * from dba_objects;
create index t on wxh_tbd(object_id);
select dbms_rowid.ROWID_RELATIVE_FNO(rowid) fn,dbms_rowid.rowid_block_number(rowid) bl, wxh_tbd.object_id,rowid from wxh_tbdwhere rownum<3;
FN BL OBJECT_ID ROWID
---------- ---------- ---------- ------------------
8 404107 20 AAAF04AAIAABiqLAAA
8 404107 46 AAAF04AAIAABiqLAAB
1) 情境1,讀讀。為了不影響實驗的完整性,我們還是來簡單的測試下讀讀的情境,雖然你可能已經知道這種情境肯定不會有buffer busy waits的等待。
SESSION 1運行:
declare
c number;
begin
for i in 1 ..6000000 loop
select count(*) into c from wxh_tbd where rowid='AAAF04AAIAABiqLAAB';
end loop;
end;
/
session 2運行:
declare
c number;
begin
for i in 1 ..6000000 loop
select count(*) into c from wxh_tbd where rowid='AAAF04AAIAABiqLAAA';
end loop;
end;
/
查看後台等待,無任何的buffer busy waits等待。這個結果是我們預料之內的。
2)情境2,寫寫:
session 1,運行:
begin
for i in 1 ..40000000 loop
UPDATE wxh_tbd SET object_name=20 where rowid='AAAF04AAIAABiqLAAA';
commit;
end loop;
end;
/
session 2,運行:
begin
for i in 1 ..40000000 loop
UPDATE wxh_tbd SET object_name=46 where rowid='AAAF04AAIAABiqLAAB';
commit;
end loop;
end;
/
兩個session的等待裡,我們都觀察到了大量的bufferbusy waits等待,由於會話1,2會在buffer 上加x排他的bufferpin,兩種鎖模式的不相容性導致了爭用。
3)讀寫混合測試:
session 1:
begin
for i in 1..40000000 loop
UPDATE wxh_tbdSET object_name=20 where rowid='AAAF04AAIAABiqLAAA';
commit;
end loop;
end;
/
session 2:
declare
c number;
begin
for i in 1..6000000 loop
select count(*)into c from wxh_tbd where rowid='AAAF04AAIAABiqLAAB';
end loop;
end;
/
session 1的等待:
1825, WAIT,latch: cache buffers chains , 3531, 882.75us
session 2的等待:
1768, WAIT,buffer busy waits , 145246, 36.31ms
我們看到發生寫的會話session 1,沒有任何的buffer busy waits等待,而發生讀的會話session 2,產生了大量的buffer busy waits等待。
網上對這一塊的爭論是比較激烈的。
道理其實非常簡單
1)當讀取的進程發現記憶體塊正在被修改的時候(如果有x模式的buffer pin,就說明正在被修改),它只能等待,它不能clone塊,因為這個時候記憶體塊正在變化過程中ing,這個時候clone是不安全的。很多人說,oracle裡讀寫是互相不阻塞的,oracle可以clone記憶體塊,把讀寫的競爭分開。其實要看情況,在讀的時候發現記憶體塊正在被寫,是不能夠clone的,因為是不安全的。這個時候讀的進程只能等待buffer busy waits。
2)當寫的進程發現記憶體塊正在被讀,這個時候,讀是不阻塞寫的,因為ORACLE可以很容易的clone出一個xcur的資料區塊,然後在clone的塊上進行寫,這個時候clone是安全的,因為讀記憶體塊的進程不會去修改資料區塊,保證了clone的安全性。
說到這裡,基本上可以來一個簡單的總結了,但是總結前,還是有必要給大家簡單介紹一下,buffer header上的兩個列表。
bufferheader上都有2個列表:users list和waiter list。
vage的文章,寫的很不錯。
QQ:252803295
DSI&Core Search Ⅱ 群:177089463(1000人技術群:未滿)
DSI&Core Search Ⅲ 群:284596437(500人技術群:未滿)
DSI&Core Search Ⅳ 群:192136702(500人技術群:未滿)
DSI&Core Search Ⅴ 群:285030382(500人閑聊群:未滿)
MAIL:
BLOG:
WEIBO:
ITPUB:
OCM: http://education.oracle.com/education/otn/YGuo.HTM