Buffer Busy Waits是怎么产生的?
作为一个Oracle Dba,如果你从未遇到过Buffer Busy Waits等待,那么你算不上一个真正的Oracle Dba。Buffer Busy Waits是Oracle 数据库非常常见的一个等待,特别是在并发写比较频繁的环境里。说起为什么会产生这个等待,首先要描述下,Oracle读写数据块的过程:
1)首先依据数据块地址计算出(HASH算法)数据块所在的HASH BUCKET。
2)根据桶的编号,计算出保护这个桶的CBC LATCH,然后申请CBC LATCH,找寻数据块在不在桶里(内存里),我们这里假设在内存里。
3)读取/修改数据块。
4)释放CBC LATCH。
以上的描述看似是非常通畅,但是存在一个问题,CBC LATCH的持有是排他的(我们暂时不考虑复杂情况:共享LATCH的持有情况),如果在排他持有CBC LATCH的情况下,读取数据块内容,那么这个LATCH的持有时间就会比较长,因为相对于LATCH的获取和释放这种CPU原子操作,读取数据块的内容是非常耗时的,因此在持有CBC LATCH的情况下,读取数据块,对于读写频繁的数据库/块,那么势必会造成CBC LATCH的争用。为了解决这个问题,Oracle引入了buffer pin(buffer lock)的功能。
我们有必要对读取数据块的内容重新做下描述,大致步骤如下:
1)首先需要判断数据块所在的HASH BUCKET。
2)然后申请CBC LATCH,定位到数据块。
3)以S/X模式获取数据块的buffer pin/lock。(读取获得s模式,修改获得x模式,S和S模式具有兼容性,S和X、X和X模式不具有兼容性)。
4)释放CBC LATCH
5)在PIN的保护下,读取/修改数据块。
6)获得CBC LATCH。
7)释放(UNPIN)BUFFER PIN(BUFFER LOCK)。
8)释放CBC LATCH。
看似步骤复杂了,CBC LATCH获取/释放了两次,可是却大大的提高了并发度。上面描述的步骤里,持有CBC LATCH的目的变得单纯,只是为了修改BUFFER的PIN模式,然后依靠PIN的模式兼容性来保护数据块,例如:S和S模式的PIN是兼容的,可以并发的读取,S和X模式是不兼容的,后来的会话需要产生等待。
虽然LATCH的持有是排他的,但是这个时间极端,引起争用的可能性不大,如果大家都是来读数据块的,那么BUFFER LOCK的S模式之间都是具有共享性的,不会产生争用。但是同一个时刻,如果一个进程以S模式持有了数据块的BUFFER LOCK,另一个进程想以X模式持有,那么就会出现争用,因为道理很简单,S模式的BUFFER LOCK和X模式的BUFFER LOCK不兼容。同理,两个同时欲修改同一个数据块的进程,也会遭遇BUFFER LOCK冲突.这个冲突以ORACLE 等待事件表示出来就是Buffer Busy Waits,也就是说Buffer Busy Waits等待的本质是buffer lock的争用导致的。
我们平时经常说读不阻塞写,写不阻塞读,那是在物理的数据块级别,在内存里,读写/写写在同一个时刻都是互相阻塞的。只有读读不阻塞。
为了方便理解,上面很多步骤做了简化,下面对某些点做些补充:
1)一旦你PIN住了一个数据块,不需要立即去UNPIN(移除PIN)它。ORACLE认为你的本次调用后还有可能去访问这个数据块,因此保留了PIN,直到本次调用结束再UNPIN。
2)Oracle在对唯一索引/undo块/唯一索引的回表/索引root、branch块的设计上,在访问(读取)的时候,获取的是共享的CBC LATCH,不需要去PIN数据块,在持有共享CBC LATCH的情况下读取数据块。可能的原因是这些块修改的可能性比较小,因此Oracle单独的采用这种机制。因此对于普通数据块的读取都是需要获取2次CBC LACTH,而对于这种特殊的数据块,只获取一次共享CBC LATCH就OK 了。
3)我们上面所说的情况都是在数据块已经存在在内存里的情况。如果数据块不在内存,有可能会产生READ BY OTHER SESSION争用等待。有时间我们再看这个等待的原因。
4)上面描述只符合10G后的版本。在10G前读读也会产生BUFFER BUSY WAITS,10G后把这方面的BUFFER BUSY WAITS归到了READ BY OTHER SESSION等待里。
5)上面的描述基本都采用了数据块这个词,更准确的表达应该是buffer block。