二、分析過程
關於gc的簡單描述,就是當實例1 發起請求時發現需要請求的塊已經在另一個實例,這個時候就需要通過GCS(LMS)通過私網把相應的塊傳遞到實例1,這個過程就是集羣的cache fusion。請求的塊又分爲當前塊和CR。
其中最常見的等待事件包括 gc buffer busy acquire、gc cr block busy。 其中gc buffer busy acquire是當session#1嘗試請求訪問遠程實例(remote instance) buffer,但是在session#1之前已經有相同實例上另外一個session#2請求訪問了相同的buffer,並且沒有完成,那麼session#1等待gc buffer busy acquire。gc cr block busy是指實例1和實例2的buffer cache都含有某個block,某個時刻實例1修改了這個block並且沒有提交;這個時刻實例2上的會話1讀取這個block需要通過undo record 來構造CR塊。且構造的過程中必須將改變記入到實例1的redo,這個時候發生的等待就是gc cr block busy等待。
這裏先使用兩個節點進行模擬下
1、實例1上面開啓一個session 對錶QXY發起300W次查詢
2、實例1上面的另開啓一個session對錶QXY同樣發起300W次查詢
3、實例2上面開啓一個session對錶QXY發起update操作
4、統計實例1上面的會話的等待事件。
-----創建表的腳本如下
create table qxy (name varchar2(20), id number, birth date) tablespace USERS;
-----插入數據量
insert into qxy select dbms_random.string('l',20), level lv, sysdate from dual connect by level < 100;
------循環語句
declare
ct number;
begin
for i in 1..3000000 loop
select count(*) into ct from qxy;
end loop;
end;
/
------創建一個新表記錄會話開始前的等待事件 start.sql
define v_inst1=&1
define v_inst2=&3
define v_sid1=&2
define v_sid2=&4
drop table begin_table;
create table begin_table as select inst_id,sid,event,total_waits
from gv$session_event
where (inst_id=&v_inst1 and sid in (&v_sid1))
or (inst_id=&v_inst2 and sid in (&v_sid2))
order by inst_id,sid,event;
------通過前後相減統計查詢之間產生的等待事件 end.sql
define v_inst1=&1
define v_inst2=&3
define v_sid1=&2
define v_sid2=&4
drop table end_table;
create table end_table as select inst_id,sid,event,total_waits from gv$session_event
where (inst_id=&v_inst1 and sid in (&v_sid1)) or (inst_id=&v_inst2
and sid in (&v_sid2)) order by inst_id,sid,event;
col event format a30
set linesize 120 pagesize 60
select ed.sid,ed.event,ed.total_waits - nvl(st.total_waits,0) diff_waits
from begin_table st,end_table ed
where st.inst_id(+)=ed.inst_id and st.sid(+)=ed.sid and
st.event(+)=ed.event order by event,sid;
----簡單測試1
實例1 session 1
實例1 session 2
實例1 session 3 先查詢會話統計信息
實例1 的session1 和 session 2 同時執行查詢語句,實例2 的session執行update語句
實例1 session1
實例1 session 2
實例2 session 1
實例1 的session 3 統計查詢結束之後的等待事件
通過上面的測試發現,如果在另外一個節點對塊進行了更新之後快速提交的話,gc cr block busy等待事件不是很高,基本都是在各位數。注意上面的update語句,雖然是30s更新一次,但是更新之後是立馬提交的,只是之後睡眠了30s。
-----簡單測試2 (如果更新之後,等待30s提交是什麼效果呢)
實例1 的session1 和 session 2 同樣同時執行查詢語句,實例2 的session執行update語句(但是這裏update是停留30s之後在提交)
實例1 session 1
實例1 session 2
實例2 session 1
實例1 session3
通過第二個測試可以看到,如果跨實例訪問塊的時候,並且塊被修改之後一直沒有提交,這個時候另外一個節點在訪問這個塊的時候,gc的相關等待會成倍數級上漲。這主要是因爲實例1和實例2的buffer cache都含有某個block,T1時刻實例2修改了這個block;T2時刻實例1上的會話1讀取這個block修改前的CR copy,因爲CR copy需要通過應用undo record才能構造出來,並且undo的訪問時候是單塊讀,所以會大大影響速度,這個過程就產生了大量的gc 等待。
所以gc cr block busy的過程如下:
1)實例1的LMS向實例1的LMS發起block 讀請求;
2)實例2的buffer cache裏已經存在了這個block修改後的最新副本C1',實例2的 LMS在本地的buffer cache里根據C1'再複製出一個新的副本 C1'',此時C1'和C1''的內容是完全相同的;
3)實例2的LMS從undo segment裏找到undo record用來applied到C1''上,把C1''回滾到修改前的狀態,記爲C1;
4)這時實例2的buffer cache 裏包含了C1',C1,其中C1是C1'修改前的內容。
5)利用undo record將C1''回滾到C1這一過程是會產生redo的,實例2 的LMS進程通知Lgwr進程把redo寫入online redolog,寫成功後才能 進入下一步
6)實例2上的Lgwr通知實例2上的LMS redo寫入完成,實例2上的LMS將C1傳輸給實例1的LMS
7)實例1的LMS將結果返回給實例1上的會話1的server process
------簡單測試3 (構造CR塊的時候需要產生REDO)
實例2 session1
執行一個update,但是不提交
實例2 session 2
統計redo size、 CR塊
實例1 session 1
查詢qxy 表,查詢實例2修改的這行記錄,這樣就可以構造CR塊。
實例2 session 2
再次統計redo size、 CR塊
可以看到CR blocks created、data blocks consistent reads - undo records applied、redo entries、redo size的值都發生了變化。
------測試 4(本地節點select遠程節點cache裏未提交的數據塊開銷到底有多大)
1、遠程節點修改後提交,本地節點兩個session併發select同一張表
實例2 session 1
實例1 session 3 統計查詢前的等待事件
實例1 session 1
實例1 session 2
實例1 session 3 統計查詢之後的等待事件
2、遠程節點修改後不提交,本地節點兩個session併發select同一張表
實例2 session 1 不提交
實例1 session 3 統計查詢前的等待事件
實例1 session 1
實例1 session 2
實例1 session 3 統計查詢之間產生的等待事件
可以看到,從實例2的修改之後立刻提交到實例2修改之後沒有提交,兩次的查詢從16s上升到了26分鐘。差距十分巨大。
同時也說明了,雖然只在實例2修改了一個塊,但是實例1的session 1 和session 2循環100W次,每次循環的時候都要構造CR,也就是說CR塊不同共用。
實例2的 TOP 情況
可以看到,實例2 CPU的使用情況, LMS使用了23%, lgwr使用了21%。LGWR也正好從反面驗證了構造CR塊的時候需要寫redo。
總結:
gc 相關的等待在我們工作中很常見,通過上面的測試可以看,同一個塊在修改和查詢時,如果運用不當的話會產生很大的異常等待,對數據庫性能造成很嚴重的影響