一.什么是buffer busy waits
当n个进程想以不兼容的模式持有内存块上的buffer pin时,就会产生buffer busy waits等待。
oracle访问/修改数据块的步骤:
1.依据数据块的地址计算出数据块所在的bucket
2.获得保护这个bucket的cbc latch
3.在这个链表上查找需要的数据块,找到后,pin这个buffer(读取s,修改x)
4.释放cbc latch
5.读取/修改数据块的内容
6.获取cbc latch
7.unpin这个buffer
8.释放cbc latch
分析:因为latch的操作都是很短的,上面步骤中除了步骤5,都可以认为是极快的操作。在大并发环境中,如果cbc latch的持有时间过长,会导致大量的latch争用,以致非常容易导致系统的cpu资源出现瓶颈。注意即使所有的操作都是查询非修改,也会导致大量的cbc latch争用:因为cbc latch的持有到cbc latch的释放这段时间太长了。oracle使用在内存块上加buffer pin的方式来解决这个问题。可以看出,这种实现方式使得cbc latch持有期间只做了很少的事情,大大降低了cbc latch的争用。
如果数据库里面读多写少,由于各个读之间的buffer pin时兼容的,都是s模式,因此几乎不会产生任何的争用。
如果数据库里面写多读少,就会产生buffer busy waits等待,但这种等待的代价比cbc latch的等待代价要小的多,因为latch的spin机制是非常耗cpu的,而buffer pin的管理本质上类似于enquen锁的机制,没有spin机制,不需要自旋转消耗大量的cpu。
二.buffer busy waits测试
创建测试表:
SQL> create table test_buffer_busy as select * from dba_objects;
Table created.
SQL> create index idx_test_buffer_1 on test_buffer_busy(object_id);
Index created.
找到同一个块上的两条记录:
SQL> select dbms_rowid.ROWID_RELATIVE_FNO(rowid) fn,dbms_rowid.rowid_block_number(rowid) bl, test_buffer_busy.object_id,rowid from test_buffer_busy where rownum<3;
FN BL OBJECT_ID ROWID
---------- ---------- ---------- ------------------
4 3131 20 AAAVXOAAEAAAAw7AAA
4 3131 46 AAAVXOAAEAAAAw7AAB
1.场景1,读读操作
session1执行:
SQL> select sid from v$mystat where rownum=1;
SID
----------
1
SQL> declare
2 c number;
3 begin
4 for i in 1 ..6000000 loop
5 select count(*) into c from test_buffer_busy where rowid='AAAVXOAAEAAAAw7AAA';
6 end loop;
7 end;
8 /
session2执行:
SQL> select sid from v$mystat where rownum=1;
SID
----------
37
SQL> declare
2 c number;
3 begin
4 for i in 1 ..6000000 loop
5 select count(*) into c from test_buffer_busy where rowid='AAAVXOAAEAAAAw7AAB';
6 end loop;
7 end;
8 /
sesson3观察是否产生buffer busy waits等待事件:
SQL> select event,sid,p1,p2,p3 from v$session_wait where sid in (1,37) and event like '%buffer%';
no rows selected
结论:读读场景不会产生任何buffer busy waits等待事件
2.场景2,写写操作
session1:
SQL> begin
for i in 1 ..40000000 loop
2 3 UPDATE test_buffer_busy SET object_name=20 where rowid='AAAVXOAAEAAAAw7AAA';
4 commit;
5 end loop;
6 end;
7 /
session2:
SQL> begin
2 for i in 1 ..40000000 loop
3 UPDATE test_buffer_busy SET object_name=46 where rowid='AAAVXOAAEAAAAw7AAB';
4 commit;
5 end loop;
6 end;
7 /
session3:
SQL> select event,sid,p1,p2,p3 from v$session_wait where sid in(46,50) and event like '%buffer%';
no rows selected
EVENT SID
---------------------------------------------------------------- ----------
P1 P2 P3
---------- ---------- ----------
buffer busy waits 46
4 3131 1
buffer busy waits 50
4 3131 1
参数解释:
p1:数据块驻留的绝对文件号。select file_name,relative_fno from dba_data_files;
p2:进程需要访问的实际数据块号。
p3:块类型编号
1 Data block
2 Sort block
3 Save undo block
4 Segment header
5 Save undo header
6 Free List
7 Extent map
8 1st level bitmap block
9 2nd level bitmap block
10 3rd levelbitmap block
11 Bitmapblock
12 Bitmapindex block
13 Fileheader block
14 Unused
15 Systemundo block
16 Systemundo block
17 Undoheader
18 Undoblock
查看v$waitstat视图:
SQL> select * from v$waitstat;
CLASS COUNT TIME
------------------ ---------- ----------
data block 10349 2399
可以看到data block上面发生了大量等待。
两个session的等待里都有大量的buffer busy waits等待,由于session1和session2在同一个buffer上加x排他的buffer pin,两种锁模式的不兼容导致了争用。
3.场景3,读写操作
session1:
SQL> begin
2 for i in 1..40000000 loop
3 UPDATE test_buffer_busy SET object_name=20 where rowid='AAAVXOAAEAAAAw7AAA';
4 commit;
5 end loop;
6 end;
7 /
session2:
SQL> declare
2 c number;
3 begin
4 for i in 1..6000000 loop
5 select count(*)into c from test_buffer_busy where rowid='AAAVXOAAEAAAAw7AAB';
6 end loop;
7 end;
8 /
session3:
SQL> select event,sid,p1,p2,p3 from v$session_wait where sid in (1,37) and event like '%buffer%';
EVENT SID
---------------------------------------------------------------- ----------
P1 P2 P3
---------- ---------- ----------
latch: cache buffers chains 1
2362473448 177 0
buffer busy waits 37
3 272 35
可以看到,session1没有任何的buffer busy waits等待,而进行读操作的session2,产生了大量的buffer busy waits等待。原理:
(1).当读取的进程发现内存块正在被修改时(x模式的buffer pin),它只能等待而不能clone,因为这个时候内存块正在变化过程中,这个时候clone是不安全的。这个时候,读的进程只能等待buffer busy waits。
(2).当写的进程发现内存块正在被读,这个时候,读时不阻塞写的,因为oracle可以很容易的clone出一个数据块,然后在clone的块上进行写,这个时候clone是安全的,因为读内存块的进程不会去修改数据块,保证了clone的安全性。
三.总结
1.buffer busy waits是产生在buffer block上的等待,由于n个进程想以不兼容的模式获得buffer block上的buffer pin时,进而引起buffer waits等待。
2.buffer block的管理模式类似enq锁,先进先出,由队列记录锁的拥有者和等待者
3.写写,读写操作都会产生buffer busy waits等待。写写的两个会话,都会产生buffer waits等待,而读写的两个会话,只有读的session会产生,因为它不能简单的clone一个数据块,正在发生写的内存块发生clone是不安全的。
4.oracle为了减少cbc latch持有时间过长的问题,以每次访问buffer block的会话获取两次cbc latch,再配合在内存块上加buffer pin来解决这个问题。