本文收集了魏兴华在itpub的精华帖,还有其他一些网友的总结整理而成
buffer busy waits 等待事件的产生
当n个进程想以不兼容的模式持有内存块上的buffer pin的时候,就会产生buffer busy waits等待
什么?
内存块上有buffer pin ?
不是说内存块的锁都是靠latch实现的吗?什么时候还冒出一个buffer pin?从来没听说过!!
好,既然你这么问,那我们可以先假设没有buffer pin这东东,看看oracle怎么去访问/修改一个数据块。(下面的过程尽可能的我做了简化)
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的,而buffer pin的管理本质上类似于enq 锁的机制,没有spin机制,不需要自旋耗费大量的cpu。
如果你的数据库是读写混合的场景,那么写会阻塞读,产生buffer busy waits,但是读不会阻塞写,不会产生这个等待。这个我们后面会重点讨论
下面我们模拟下怎么产生busy buffer waits等待事件:
SCOTT@orcl>select dbms_rowid.ROWID_RELATIVE_FNO(rowid) fn, dbms_rowid.rowid_block_number(rowid) bl, rowid from emp ; FN BL ROWID---------- ---------- ------------------ 4 151 AAAR3xAAEAAAACXAAA 4 151 AAAR3xAAEAAAACXAAB 4 151 AAAR3xAAEAAAACXAAC 4 151 AAAR3xAAEAAAACXAAD 4 151 AAAR3xAAEAAAACXAAE 4 151 AAAR3xAAEAAAACXAAF 4 151 AAAR3xAAEAAAACXAAG 4 151 AAAR3xAAEAAAACXAAH 4 151 AAAR3xAAEAAAACXAAI 4 151 AAAR3xAAEAAAACXAAJ 4 151 AAAR3xAAEAAAACXAAK FN BL ROWID---------- ---------- ------------------ 4 151 AAAR3xAAEAAAACXAAL 4 151 AAAR3xAAEAAAACXAAM 4 151 AAAR3xAAEAAAACXAAN
可以看到,emp表上的所有行都是在file 4,block 151上面的
现在我们来模拟三种情况:
情况一:两个session并发的进行查询
SCOTT@orcl>select distinct sid from v$mystat; SID---------- 164 SCOTT@orcl>declare 2 c number; 3 begin 4 for i in 1 ..6000000 loop 5 select count(*) into c from emp where empno=7788; 6 end loop; 7 end; 8 /SCOTT@orcl>select distinct sid from v$mystat; SID---------- 422 SCOTT@orcl>declare 2 c number; 3 begin 4 for i in 1 ..6000000 loop 5 select count(*) into c from emp where empno=7900; 6 end loop; 7 end; 8 /
这个时候查询当前的非空等待事件:
SYS@orcl>select distinct event,s.wait_class from v$session_wait s,v$event_name e where event=name and e.wait_class not like 'Idle'; EVENT WAIT_CLASS---------------------------------------- ----------------------------------------SQL*Net message to client Network
可以看到没有buffer busy waits的等待事件
情况二:一个session进行查询,另一个session进行更新操作:
SCOTT@orcl>select distinct sid from v$mystat; SID---------- 422 SCOTT@orcl>SCOTT@orcl>begin 2 for i in 1 ..4000000 loop 3 UPDATE emp SET sal=2000 where empno=7788; 4 commit; 5 end loop; 6 end; 7 /
SCOTT@orcl>select distinct sid from v$mystat; SID---------- 85SCOTT@orcl>declare 2 c number; 3 begin 4 for i in 1 ..6000000 loop 5 select count(*) into c from emp where empno=7900; 6 end loop; 7 end; 8 /
在这样的情况下系统的等待事件:
SYS@orcl>select distinct event,s.wait_class from v$session_wait s,v$event_name e where event=name and e.wait_class not like 'Idle'; EVENT WAIT_CLASS------------------------------ ------------------------------latch free Otherlog file parallel write System I/Ocontrol file parallel write System I/OSQL*Net message to client Network
情况三:两个session同时并发的进行更新操作
session 1:
SCOTT@orcl>begin 2 for i in 1 ..4000000 loop 3 UPDATE emp SET sal=2000 where empno=7788; 4 commit; 5 end loop; 6 end; 7 /
session 2:
SCOTT@orcl>begin 2 for i in 1 ..4000000 loop 3 UPDATE emp SET sal=2000 where empno=7900; 4 commit; 5 end loop; 6 end; 7 /
查询系统的当前等待事件:
EVENT WAIT_CLASS------------------------------ ------------------------------log file switch (checkpoint in Configurationcomplete) control file parallel write System I/Obuffer busy waits ConcurrencySQL*Net message to client Network
看到了buffer busy waits的等待事件
我们看到发生写的会话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上的两个列表
每个buffer header上都有2个列表:users list和waiter list。
users list用来记录,当前有哪些会话获得了此buffer block上的buffer pin,并记录下buffer pin的模式。
waiter list用来记录,当前有哪些会话在等待buffer block 上的buffer pin,并记录下申请buffer pin的模式。
看到这两个列表,是不是觉得似曾相识?对了,enq锁的管理也是类似的这个方式,不过多了一个列表,锁转换列表。
给大家举个例子,会更清晰一些:
session 1(sid 123):修改数据块block 1
此block的buffer headler上的users list如下:
sid hold mode
123 x
session 2(sid 134):也想修改数据块block 1,但是由于于session 1的锁模式不兼容,只能等待buffer busy waits,此时的user list不变,waiter list如下:
sid req mode
134 x
session 3(sid 156):也想修改数据块block 1,但是由于于session 1的锁模式不兼容,只能等待buffer busy waits,如果这个时候我们去观察后台的等待,会发现2个会话在等待buffer busy waits了(134,156)。此时的user list不变,waiter list如下:
sid req mode
134 x
156 x
如果这个时候sid为123的会话修改完成,那么会通知sid为134的会话获得buffer pin,此时的user list,waiter list 如下:
user list
sid hold mode
134 x
waiter list
sid req mode
156 x
可要看到,buffer lock的这种机制非常类似于enq锁的机制,先进先出,然后通过n个列表来记录锁的拥有者和等待着。等待buffer busy waits的进程在进入队列后,会设置一个1秒(_buffer_busy_wait_timeout)的超时时间,等待超时后,会“出队”检查锁有没有释放,然后重新入队。
最后我们可以来一个总结了:
1)buffer busy waits是产生在buffer block上的等待,由于n个进程想以不兼容的模式获得buffer block的buffer pin,进而引起buffer busy waits等待。
2)buffer lock的管理模式非常类似enq锁的管理模式,先进先出,有队列去记录锁的拥有者和等待着。
3)写写,读写都会产生buffer busy wiats等待。写写的两个会话,都会产生buffer busy wiaits等待,而读写的两个会话,只有读的session会产生,因为它不能去简单的clone一个内存块,正在发生写的内存块发生克隆是不安全的
4)oracle为了解决cbc latch持有时间过长的问题,以每次访问buffer block的会话获取两次cbc latch,再配合在内存块上加buffer pin来解决这个问题。
说明:oracle并不是针对所有的内存块都采取两次获取cbc latch的机制,比如针对索引root,索引branch,唯一索引的叶子节点,都是采取的一次获取机制
buffer busy waits的处理步骤:
1、定位当前buffer busy waits的热块:
select event,p1,p1text,p2,p2text,p3,p3text from v$session_wait where wait_class not like 'Idle';EVENT P1 P1TEXT P2 P2TEXT P3 P3TEXT---------------------------------------- ---------- -------------------- ---------- -------------------- ---------- --------------------latch: cache buffers chains 2.5253E+10 address 150 number 0 triesdirect path read 7 file number 1581054 first dba 1 block cntdirect path read 7 file number 1609013 first dba 4 block cntlog file parallel write 1 files 18 blocks 1 requestsdirect path read 7 file number 1607515 first dba 1 block cntSQL*Net message to client 1650815232 driver id 1 #bytes 0buffer busy waits 4 file# 151 block# 1 class#Disk file operations I/O 3 FileOperation 0 fileno 4 filetype
我们可以看到,造成buffer busy waits热块的是file 4 block 151这个数据块
下一步我们再来查找这个数据块是属于哪个段的
Select owner, segment_name, segment_type, partition_name,tablespace_name from dba_extentswhere relative_fno='&filename' and '&blocknum' between block_id and (block_id+blocks-1)OWNER SEGMENT_NAME SEGMENT_TYPE PARTITION_NAME TABLESPACE_NAME-------------------- ------------------------------ ------------------------------ ------------------------------ ------------------------------SCOTT EMP TABLE
|