二、分析过程
关于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 相关的等待在我们工作中很常见,通过上面的测试可以看,同一个块在修改和查询时,如果运用不当的话会产生很大的异常等待,对数据库性能造成很严重的影响