Buffer Busy Waits是Oracle 数据库常见的一个等待,特别是在并发写比较频繁的环境里。作为一个Oracle DBA,如果你从未遇到过Buffer Busy Waits等待,那么你算不上一个真正的Oracle DBA。
产生这个等待的原因是,在内存级别,在同一时刻对同一内存块进行读写产生了争用。这里我以2个进程对数据块的操作为例,归纳出产生Buffer Busy Waits的场景:
写写,例如一个进程正在对内存块做更改,此时另一个进程也要修改这个内存块
写读,例如一个进程正在对内存块做更改,另一个进程此时想要读这个内存块。你可能会问,oracle为什么不去copy正在修改的块构造CR块,然后去读构造出的CR块,理由很简单,内存块正在写(变化)的过程中去做copy,是一个不安全的动作。
按照字符的排列组合,还剩”读读“、”读写“两个场景,这两个场景都不会导致产生Buffer Busy Waits(这里很重要的一个假设是2个进程),理由我们后面再给出,这里按住不表。
Oracle为什么要设计这个等待,这是个好问题,本篇文章也主要是为了解答这个疑问。
“为什么”要比“是这样做的”的更重要,前者讲的是逻辑和洞见,后者描述的是过程和结果。
回答这个问题之前,首先设计一个场景,“假如没有这个等待,读写数据块是如何进行的”,当然这个场景是设计出来的,其实并不存在。
那我们开始,假设场景是这样子的,我们要对一个数据块做写入操作,遵循如下步骤:
首先依据数据块地址计算出(Hash算法)该块所在的Hash Bucket。
根据桶的编号,计算出保护这个桶的CBC Latch,然后申请CBC Latch,查找数据块在不在桶里,这里假设在内存里。
修改数据块。
释放CBC Latch。
以上的描述看似是非常通畅,但是存在一个问题,由于CBC Latch的持有是排他的,在持有CBC Latch的情况下,去修改数据块,那么这个Latch的持有时间就会比较长,这个长是相对于Latch的获取和释放这种CPU原子操作的,因此在持有CBC Latch的情况下修改数据块,对于读写频繁的数据库/块(热点块),那么势必会造成CBC Latch的争用,基于Latch的特性(自旋、休眠、再自旋),会造成大量CPU资源的浪费,导致数据库的性能低下。
为了解决这个问题,Oracle设计了Buffer Pin的功能。也就是说,Buffer Pin这个机制存在的价值,是为了降低CBC Latch的争用,节省CPU资源。
引入Buffer Pin后,修改数据块的大致步骤如下:
首先依据数据块地址计算出(Hash算法)该块所在的Hash Bucket。
然后申请CBC Latch,查找、定位到数据块
以X模式获取数据块的Buffer Pin。
释放CBC Latch
在Pin的保护下,修改数据块,完成后继续以下步骤
获得CBC Latch
释放Buffer Pin
释放CBC Latch
步骤复杂了许多,CBC Latch获取/释放共发生了两次,工作量似乎整整大了两倍。
可是极大程度降低了CBC latch的争用。 因为持有CBC Latch的时间变得极短。 持有CBC Latch,只是为了在buffer header上增加buffer pin,目的变得单纯和简单。
你可能会说,Oracle这种解决方案就是按了葫芦起了瓢,它只不过是转移了竞争,以前是CBC Latch的争用,现在是Buffer Pin的争用。
这句话并没错。
但是Buffer Pin的争用是不消耗CPU资源的。类似于队列锁的通知机制。而不会像Latch一样去做自旋。
读取数据块增加S模式的Buffer Pin,修改数据块增加X模式的Buffer Pin。S和S模式的Pin是兼容的,可以并发的读取,X和S模式是不兼容的,后来的读取会话需要产生等待。
文章的开头,我们举了2个进程操作同一内存块的例子,这里我们在有了一些知识之后,再做下总结和回顾。
写读,同一个时刻,如果一个进程以X模式持有了数据块的Buffer Pin,另一个进程想以S模式持有,那么就会出现争用,因为道理很简单,X模式的Buffer Pin和S模式的Buffer Pin不兼容。
写写,同理,两个同时欲修改同一个数据块的进程,只有一个进程可以获取X模式Buffer Pin,另一个会话产生Buffer Busy Waits等待。
我们平时经常说读不阻塞写,写不阻塞读,那是在物理的数据块级别,在内存里,在同一时刻对于同一个内存块的写读/写写都是互相阻塞的。
这里接着上面把场景补充完整,“读读”不会产生Buffer Busy Waits等待,因为Pin模式是兼容的。“读写”不会产生Buffer Busy Waits等待,一个进程正在读数据块,此时另一个进程要去写这个数据块,写进程很聪明,它会copy出一个current块出来(之前的块成为CR块),然后在current块上进行写操作,通过这种方式避免了竞争,规避了Buffer Busy Waits的争用。
因为前一个进程是在读取,而不是修改。这个场景下的copy就是安全的。
另外一点,因为读取数据库块需要在Buffer header上增加S模式的Buffer Pin,属于内存的修改操作,因此即使是读取操作,CBC Latch的持有也是排他的,但是这个增加Pin的时间极短,在压力正常的数据库环境中引起CBC Latch大量争用的可能性不大。
当然如果是大量进程对同一内存块频繁读取,就会引起CBC Latch的争用。
最后,有必要对一些细节再做些补充:
一旦进程Pin住了一个数据块,不需要立即去UNPin(移除Pin)。ORACLE认为在本次调用后还有可能继续去访问这个数据块,因此直到本次调用结束才会做UNPin操作。
Oracle在对唯一索引/undo块/唯一索引的回表/索引root、branch块的设计上,在访问(读取)的时候,获取的是共享的CBC Latch,不需要去对Buffer加Pin,直接在持有共享CBC Latch的情况下读取数据块。这样做的原因是这些类型的块,发生修改的可能性比较小,因此Oracle单独的采用这种机制。因此对于普通数据块的读取都是需要获取2次CBC Latch,而对于这种特殊的数据块,只获取一次共享CBC Latch就可以了。
我们上面所说的情况都是在数据块已经存在在内存里的情况。如果数据块不在内存,有可能会产生Read By Other Session争用等待。
上面描述只符合10G后的版本。在10G前读读也会产生Buffer Busy Waits,10G后把这方面的Buffer Busy Waits归到了Read By Other Session等待里。
本文讲述了Buffer Busy Waits是如何产生的及其机制,但是并没有讲解如何对其进行调优。Buffer Busy Waits等待设计的本质是为了降低CBC Latch的争用。以两个进程操作内存块为例,在“写写”、“写读”场景下会产生Buffer Busy Waits,在“读读”场景下不会产生Buffer Busy Waits等待,在“读/写”场景下,发生写操作的服务器进程会去Copy出一个current块来继续写操作,而不会去等待Buffer Busy Waits。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。