对Buffer Busy Waits进行了次深入的分析,得到结论如下:
一、兼容模式:
10G后的Buffer Busy Waits已经和以前大不一样了。不再是以前的读、写互相阻塞,或写、写互相阻塞。也不是有些网友说的读、写不阻塞,只有写、写阻塞。
真实的Buffer Busy Waits,有如下两种情况:
(1)、两个进程一同写一个Buffer,这是写、写阻塞。
(2)、一个进程在写一个Buffer,另一个进程读。读的进程会有等待。这是写阻塞读。
现在,Buffer Busy Waits,只有这两种情况了。
有人说,如果一个进程先开始读一个Buffer,另一个进程又要写一个Buffer,这种情况下Oracle如何处理?读会阻塞写吗?答案是,不会。写的操作可以不需等待的马上开始,而正在进行的读操作,则会中止开始等待Buffer Busy Waits。这就是为什么在Buffer busy waits升高时,查找SQL语句,等待Buffer busy waits的大多数会话都是Select,很少有DML。因为DML不会被Select阻塞,只有DML间会互相阻塞。关于一点,做个简单的测试就可以证明:
在会话1运行如下脚本:
declare
j number;
begin
for i in 1..3000000 loop
select id into j from a1 where rowid='AAAChqAAEAAAAAMAAA';
end loop;
end;
/
反复读取某一行。
在会话2运行如下脚本:
begin
for i in 1..300 loop
update a1 set id=id+0 where rowid='AAAChqAAEAAAAAMAAA';
end loop;
commit;
end;
/
然后在V$SESSION_EVENT中查看两个Session的Buffer busy waits等待。
会话1反复查询一行,会话2反复更新同一行300次,两个会话应该各有少许的Buffer busy waits,对吧。
但实际情况不是这样的。会话2一般不会有,只有会话1会有Buffer busy wait等待。
这就是写阻塞读、读在大部分情况下,不会阻塞写。当然,除了这种证明方法外,还有更加精确的方法,证明写阻塞读、读不阻塞写,我放在后面深入部分。
二、减少Buffer busy waits的另类方法
我在前一篇文章中,提到从宏观上减少Buffer busy waits等待的另一种方法,就是加快日志文件的写速度,使用更快的设备,放存Redo文件。原理是什么呢:
进程在Buffer Cache中的某个Buffer加的锁,我称之为Buffer Pin。进程修改一个Buffer的过程,可以粗略的描述如下:
(1)、加Cache buffers chains latch(下文中简称为CBC Latch)
(2)、加Buffer Pin
(3)、释放CBC Latch。
(4)、将Redo信息拷贝到共享池的公有Redo区,或Log Buffer中。
(5)、修改Buffer中的内容
(6)、加CBC Latch
(7)、释放Buffer Pin
(8)、释放CBC Latch
1至3步,加CBC Latch,是为了加Buffer Pin。6至8步的CBC Latch,是释放Buffer pin。
在加了Buffer Pin之后,主要要做的事情有两个,一个是修改Buffer中的内容。另一个,是拷贝Redo信息。
减少Buffer busy waits,其实就是缩短Buffer Pin的持有时间。缩短Buffer Pin的持有时间,我们可以做的事情有两个,一个是加快第5步,“修改Buffer中的内容”。或者,是第4步,拷贝Redo信息。在第5步,Oracle修改Buffer中内容,使用的是memcpy函数,这一步没什么好优化的了。但第4步拷贝Redo信息,还是可以调优的。在一定条件下,前台进程要将Redo信息直接拷贝到Log Buffer,而不是共享池中的私有Redo区。此时,如果Log Buffer中没有空间,遇到Log Buffer Space等待,前台进程要呼叫LGWR刷新Log Buffer。一旦出现这种情况,Buffer Pin的持有时间不得不变的很长,直到Log buffer中有空间了,拷贝Redo信息结束,前台进程修改完Buffer中的内容。然后才会释放Buffer pin。
因此,如果我们可以保证Log Buffer中总是有空间可用,就可以减少Log Buffer Space,从而减少前台进程呼叫LGWR的次数,从而在某些时候减少Buffer Pin的持有时间。