一.什么是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
两个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来解决这个问题。