Oracle读取数据块的时候,如果数据块已经在内存里了,那么首先需要根据数据块的DBA即地址,HASH出它在哪一个桶(hash bucket)里,cache buffer chains 把桶里的数据块串了起来,如果想要访问数据块,需要获得cache buffer chain latch,这个latch是用来保护数据块的,获得latch以后,pin数据块。(一致性读,事务一致性保证,后一个事务修改的同一个数据块不会被先提交(不光不会提交,连想改都要等)导致上一个事务再次修改)
buffer busy waits就发生在pin数据块的过程里,读读,读写都不会有冲突,也就是都兼容。可是写写的话,就会出现buffer busy waits。
几乎大多数的人都误解了,以为buffer busy waits事件表明读的太频繁了,其实不是。而是写(修改)的太频繁了。
我想之所以有误解,是因为10G以前,把一个等待事件read by other session也划入到了buffer busy waits里,read by other session的意思是,多个session并发请求相同的数据块,但因该数据块不在buffer_cache中而必须从磁盘读取,处理这种情况,oracle会只让其中一个sesion进行磁盘读取,此时其它session等待块从磁盘上读取进buffer_cache而抛出read by other session等待事件。10G以后,read by other session被单独了出来,buffer busy waits变的纯粹,就是上面我说的那种情况了。
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的保护下,读取/修改数据块。(修改数据时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。(oracle就算只改一行,也是操作整个块的)
1. ORACLE 访问Data buffer block的过程:
1)依据数据块的地址计算出数据块所在的bucket
2)获得保护这个bucket的cbc latch
3)在这个链表上找寻我们需要的数据块(没有的话需要物理IO),找到后,pin这个buffer(读取s,修改x)
4)释放cbc latch
5)读取/修改数据块的内容
6)获取cbc latch
7)unpin这个buffer
8)释放cbc latch
两次需要latch 只是用来pin和unpin
2.buffer busy wait 等待事件形成原因:
(1). 读读
当一个session 读取一个block时,该block在cache中不存在,需要从disk读取至cache,此时其他session 想要读取该block时,就会发生buffer busy wait 等待事件。
(2). 写写
当一个session 修改block时,首先会对该block 进行buffer pin(修改x),因此其他session在对该block进行修改时都要等待上一个session unpin buffer。
(3). 读写
1)当读取的进程发现内存块正在被修改的时候(如果有x模式的buffer pin,就说明正在被修改),它只能等待,它不能clone块,因为这个时候内存块正在变化过程中ing,这个时候clone是不安全的。很多人说,oracle里读写是互相不阻塞的,oracle可以clone内存块,把读写的竞争分开。其实要看情况,在读的时候发现内存块正在被写,是不能够clone的,因为是不安全的。这个时候读的进程只能等待buffer busy waits。
2)当写的进程发现内存块正在被读,这个时候,读是不阻塞写的,因为ORACLE可以很容易的clone出一个xcur的数据块,然后在clone的块上进行写,这个时候clone是安全的,因为读内存块的进程不会去修改数据块,保证了clone的安全性。
(4).
3.buffer busy wait 常见发生原因
(1). 性能差的QUERY访问相同的block 并发执行时,产生大量的物理读。
(2). freelist 设置过小,导致并发insert table时,频繁扫描freelist,产生争用。-已过期
(3). 大量session 并发修改相同的index block
4.常用解决方法
(1). tuning sql 减少物理读
(2). 删除一些hot row 并重新insert 至其他block中--- 不能保证啊,不过如果块多可以平均分配
(3). 如果table 较小,可以考虑将其数据 cache至keep data buffer中
(4). 减少low cardinality index的使用。 避免index block 争用。 --低基数就是大量重复的,记录在一个B+树的叶子节点上
(5). 增加extents size。 避免oracle频繁分配空间而造成的extent map 争用。
新创建的表,还没分配任何区,如果此时allocate手动分配区大小64k,8个block,刚好初始一个区的大小。不会分配1个区,而是2个区。即第一个区是初始区,默认还会再分配一个。
allocate,默认会先分配一个段头,8个block,然后会按128个block进行分配
1.allocate是为段手动分配区的命令。一般默认情况下,当我们使用完一个区的时候,oracle会自动为我们再分配一个区。
2、我们可以在插入大量数据时,可以手动分配一下区,这样在插入的时候节省了分配区的时间,能改善Oracle的性能,提高插入的速度。
3、我们可以对以下数据表进行统计分析,分析一些插入数据频繁的表,估算出一个周期的增长量,根据增长量,我们预先分配好区,来提升性能。
4、allocate手动指定了分配的区总大小,但是每个区的大小,默认还是由oracle决定,即还是遵循系统管理区大小原则。
alter table zhuo.t1 allocate extent(size 1G);
6 pct free pct used 设置。
5. 产生等待的block 类型及解决方法
Block Type | Possible Actions |
data blocks | Eliminate HOT blocks from the application. Check for repeatedly scanned / unselective indexes. Change PCTFREE and/or PCTUSED. Check for 'right- hand-indexes' (indexes that get inserted into at the same point by many processes). Increase INITRANS. Reduce the number of rows per block. |
segment header | Increase of number of FREELISTs. Use FREELIST GROUPs (even in single instance this can make a difference). |
freelist blocks | Add more FREELISTS. In case of Parallel Server make sure that each instance has its own FREELIST GROUP(s). |
undo header | Add more rollback segments. |
6.相关命令
SELECT p1 "File", p2 "Block", p3 "Reason"
FROM v$session_wait
WHERE event='enq: TX - row lock contention'; buffer busy wait
select objd, file#,block#,class#,ts#,cachehint,status,dirty from v$bh where file#=546 and block#=1289912;
select * from dba_objects where object_id = 124269
select current_obj#,obj.object_name,count(*) from dba_hist_active_sess_history , dba_objects obj
where event='buffer busy waits'
and sample_time>=to_date('2027-07-09 00:00:00','yyyy-mm-dd hh24:mi:ss')
and sample_time<=to_date('2027-07-10 00:00:00','yyyy-mm-dd hh24:mi:ss')
and obj.data_object_id = current_obj#
group by current_obj#,obj.object_name
--RAC 环境
gc buffer busy等待事件
gc buffer busy acquire/release 往往是 gc current block busy的衍生产品, 当同一实例内的多个进程并发地访问同一个数据块时 ,首先发起的进程 将进入 gc current block busy的等待 ,而在 buffer waiter list 上的后续进程 会陷入gc buffer busy acquire/release 等待(A user on the same instance has started a remote operation on thesame resource and the request has not completed yet or the block was requestedby another node and the block has not been released by the local instance whenthe new local access was made), 这里存在一个排队效应, 即 gc current block busy是缓慢的,那么在 排队的gc buffer busy acquire/release就会更慢。
2. 问题分析
(1).查看过去一段时间的等待事件类型汇总
- select wait_class_id, wait_class, count(*) cnt
- from dba_hist_active_sess_history
- where snap_id between 12831 and 12838
- group by wait_class_id, wait_class
- order by 3;
(2).可以更细化为具体的等待事件
- SELECT event_id, event, COUNT (*) cnt
- FROM dba_hist_active_sess_history
- WHERE snap_id BETWEEN 12831 AND 12838 AND wait_class_id = 3871361733
- GROUP BY event_id, eventorder
- ORDER BY 3;
(3).查看出是哪些sql引起的
- SELECT sql_id, COUNT (*) cnt
- FROM dba_hist_active_sess_history
- WHERE snap_id BETWEEN 12831 AND 12838 AND event_id IN (1478861578)
- GROUP BY sql_idhaving
- HAVING COUNT (*) > 1000
- ORDER BY 2;
(4).需要明确该sql等待的对象
- SELECT current_obj#, COUNT (*) cnt
- FROM dba_hist_active_sess_history
- WHERE snap_id BETWEEN 12831 AND 12838
- AND event_id = 1478861578
- AND sql_id = '8v3b2m405atgy'
- GROUP BY current_obj#
- ORDER BY 2;
(5).进一步确认那些块竞争最激烈
- SELECT current_file#, current_block#, COUNT (*) cnt
- FROM dba_hist_active_sess_history
- WHERE snap_id BETWEEN 12831 AND 12838
- AND event_id = 1478861578
- AND sql_id = '8v3b2m405atgy'
- AND current_obj# IN (3208618, 3208619, 3208620)
- GROUP BY current_file#, current_block#
- HAVING COUNT (*) > 50
- ORDER BY 3;
(6).检查一下这些块是否为段头块
- SELECT segment_name, header_file, header_block
- FROM dba_segments
- WHERE wner = 'JHEIDER'
- AND partition_name = 'P_2007_09'
- AND segment_name IN
- ('PLACEMENTS_LOG',
- 'PLCMNTS_LG_X_ID',
- 'PLCMNTS_LG_X_CHANGE_DATE');
3. 优化方法
(1).hot block
(2).优化SQL