查看: 31853|回复: 79

[精华] buffer busy waits,我的看法

[复制链接]
论坛徽章:
21
奔驰
日期:2013-08-06 15:23:05日产
日期:2013-08-07 22:56:38蜘蛛蛋
日期:2012-12-29 19:15:08奥迪
日期:2013-08-07 17:02:24数据库板块每日发贴之星
日期:2010-06-28 01:01:03奥迪
日期:2013-08-13 10:10:28本田
日期:2013-11-20 15:17:02优秀写手
日期:2013-12-18 09:29:08玉兔
日期:2014-03-04 16:47:17铁扇公主
日期:2012-02-21 15:02:40
跳转到指定楼层
1#
发表于 2013-7-8 12:59 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 wei-xh 于 2013-7-19 16:40 编辑

最近看到论坛里好几篇帖子在讨论buffer busy waits,在这里谈谈我的看法。
先说说这个等待怎么来的。
buffer busy waits 的由来。
当n个进程想以不兼容的模式持有内存块上的buffer pin的时候,就会产生buffer busy waits等待。
什么?
内存块上有buffer pin ?
不是说内存块的锁都是靠latch实现的吗?什么时候还冒出一个buffer pin?从来没听说过!!
好,既然你这么问,那我们可以先假设没有buffer pin这东东,看看oracle怎么去访问/修改一个数据块。(下面的过程尽可能的我做了简化)
1)依据数据块的地址计算出数据块所在的bucket
2)获得保护这个bucket的cbc latch
3)在这个链表上找寻我们需要的数据块
4)读取/修改数据块的内容
5)释放cbc latch
我们知道latch的获得和释放时间一般都是极短的(cpu的原子操作),上面5个步骤里1,2,3,5的时间我们都可以认为是极快的操作。
但是步骤四消耗的时间相对于这几个就大了去了。我粗糙的画了一个图,可以参展一下。

这样就导致了一个问题,在大并发环境下,由于cbc latch的持有时间过长,会导致大量的latch争用,latch的大量争用非常容易导致系统的cpu资源出现瓶颈。需要特别说明的是,即使你所有的操作都是查询非修改,也会导致大量的cbc latch争用:cbc latch的持有到cbc latch的释放这段时间过长了。
如何解决这个问题呢,说一下ORACLE的做法。
ORACLE通过让每次访问buffer block的会话获取两次cbc latch,再配合在内存块上加buffer pin来解决这个问题。







看如下的步骤。
1)依据数据块的地址计算出数据块所在的bucket
2)获得保护这个bucket的cbc latch
3)在这个链表上找寻我们需要的数据块,找到后,pin这个buffer(读取s,修改x)
4)释放cbc latch
5)读取/修改数据块的内容
6)获取cbc latch
7)unpin这个buffer
8)释放cbc latch
通过这种实现方式,我们看到cbc latch的持有时间大大降低了,因为cbc latch的持有,只做了很少的事情,这样就大大降低了cbc latch的争用。
你可能会挑战说,虽然cbc latch的争用会大大减轻,可是ORACLE只不过是转移了竞争点,现在变成了buffer lock之间的竞争。
你说的对,但是也不对!!
如果你的数据库里读极多,写极少,由于各个读之间的buffer pin是兼容的,都是s模式,因此不会产生任何的争用。
如果你的数据库里写极多,读极小,就会产生buffer busy waits等待,但是这种等待的代价比cbc latch的等待代价要小的多,latch的spin机制是非常耗cpu的,而buffer pin的管理本质上类似于enq 锁的机制,没有spin机制,不需要自旋耗费大量的cpu。
如果你的数据库是读写混合的场景,那么写会阻塞读,产生buffer busy waits,但是读不会阻塞写,不会产生这个等待。这个我们后面会重点讨论。

我们可以来简单的看一下,产生buffer busy waits的几个场景。如下代码找到了在同一个块上的两条记录。

create table wxh_tbd as select * from dba_objects;
create index t on wxh_tbd(object_id);
select dbms_rowid.ROWID_RELATIVE_FNO(rowid) fn, dbms_rowid.rowid_block_number(rowid) bl, wxh_tbd.object_id,rowid from wxh_tbd where rownum<3;

FN BL OBJECT_ID ROWID
---------- ---------- ---------- ------------------
8 404107 20 AAAF04AAIAABiqLAAA
8 404107 46 AAAF04AAIAABiqLAAB

1)场景1,读读。为了不影响实验的完整性,我们还是来简单的测试下读读的场景,虽然你可能已经知道这种场景肯定不会有buffer busy waits的等待。
SESSION 1运行:
declare
c number;
begin
for i in 1 ..6000000 loop
select count(*) into c from wxh_tbd where rowid='AAAF04AAIAABiqLAAB';
end loop;
end;
/
session 2运行:
declare
c number;
begin
for i in 1 ..6000000 loop
select count(*) into c from wxh_tbd where rowid='AAAF04AAIAABiqLAAA';
end loop;
end;
/

查看后台等待,无任何的buffer busy waits等待。这个结果是我们预料之内的。

2)场景2,写写:
session 1,运行:
begin
for i in 1 ..40000000 loop
UPDATE  wxh_tbd SET object_name=20 where rowid='AAAF04AAIAABiqLAAA';
commit;
end loop;
end;
/

session 2,运行:
begin
for i in 1 ..40000000 loop
UPDATE  wxh_tbd SET object_name=46 where rowid='AAAF04AAIAABiqLAAB';
commit;
end loop;
end;
/
两个session的等待里,我们都观察到了大量的buffer busy waits等待,由于会话1,2会在buffer 上加x排他的buffer pin,两种锁模式的不兼容性导致了争用。


3)读写混合测试:
session 1:
begin
for i in 1 ..40000000 loop
UPDATE  wxh_tbd SET object_name=20 where rowid='AAAF04AAIAABiqLAAA';
commit;
end loop;
end;
/

session 2:
declare
c number;
begin
for i in 1 ..6000000 loop
select count(*) into c from wxh_tbd where rowid='AAAF04AAIAABiqLAAB';
end loop;
end;
/

session 1的等待:
1825,  WAIT, latch: cache buffers chains                               ,          3531,   882.75us
session 2的等待:
1768,  WAIT, buffer busy waits                                           ,        145246,    36.31ms

我们看到发生写的会话session 1,没有任何的buffer busy waits等待,而发生读的会话session 2,产生了大量的buffer busy waits等待。
网上对这一块的争论是比较激烈的。
道理其实非常简单
1)当读取的进程发现内存块正在被修改的时候(如果有x模式的buffer pin,就说明正在被修改),它只能等待,它不能clone块,因为这个时候内存块正在变化过程中ing,这个时候clone是不安全的。很多人说,oracle里读写是互相不阻塞的,oracle可以clone内存块,把读写的竞争分开。其实要看情况,在读的时候发现内存块正在被写,是不能够clone的,因为是不安全的。这个时候读的进程只能等待buffer busy waits。
2)当写的进程发现内存块正在被读,这个时候,读是不阻塞写的,因为ORACLE可以很容易的clone出一个xcur的数据块,然后在clone的块上进行写,这个时候clone是安全的,因为读内存块的进程不会去修改数据块,保证了clone的安全性。

说到这里,基本上可以来一个简单的总结了,但是总结前,还是有必要给大家简单介绍一下,buffer header上的两个列表。

每个buffer header上都有2个列表:users list和waiter list。
users list用来记录,当前有哪些会话获得了此buffer block上的buffer pin,并记录下buffer pin的模式。
waiter list用来记录,当前有哪些会话在等待buffer block 上的buffer pin,并记录下申请buffer pin的模式。
看到这两个列表,是不是觉得似曾相识?对了,enq锁的管理也是类似的这个方式,不过多了一个列表,锁转换列表。
给大家举个例子,会更清晰一些:
session 1(sid 123):修改数据块block 1
此block的buffer headler上的users list如下:
sid  hold mode
123  x

session 2(sid 134):也想修改数据块block 1,但是由于于session 1的锁模式不兼容,只能等待buffer busy waits,此时的user list不变,waiter list如下:
sid  req mode
134  x

session 3(sid 156):也想修改数据块block 1,但是由于于session 1的锁模式不兼容,只能等待buffer busy waits,如果这个时候我们去观察后台的等待,会发现2个会话在等待buffer busy waits了(134,156)。此时的user list不变,waiter list如下:
sid  req mode
134  x
156  x

如果这个时候sid为123的会话修改完成,那么会通知sid为134的会话获得buffer pin,此时的user list,waiter list 如下:
user list
sid  hold mode
134  x

waiter list
sid  req mode
156  x

可要看到,buffer lock的这种机制非常类似于enq锁的机制,先进先出,然后通过n个列表来记录锁的拥有者和等待着。等待buffer busy waits的进程在进入队列后,会设置一个1秒(_buffer_busy_wait_timeout)的超时时间,等待超时后,会“出队”检查锁有没有释放,然后重新入队。

最后我们可以来一个总结了:
1)buffer busy waits是产生在buffer block上的等待,由于n个进程想以不兼容的模式获得buffer block的buffer pin,进而引起buffer busy waits等待。
2)buffer lock的管理模式非常类似enq锁的管理模式,先进先出,有队列去记录锁的拥有者和等待着。
3)写写,读写都会产生buffer busy wiats等待。写写的两个会话,都会产生buffer busy wiaits等待,而读写的两个会话,只有读的session会产生,因为它不能去简单的clone一个内存块,正在发生写的内存块发生克隆是不安全的
4)oracle为了解决cbc latch持有时间过长的问题,以每次访问buffer block的会话获取两次cbc latch,再配合在内存块上加buffer pin来解决这个问题。

说明:oracle并不是针对所有的内存块都采取两次获取cbc latch的机制,比如针对索引root,索引branch,唯一索引的叶子节点,都是采取的一次获取机制。具体可以参照vage的帖子,写的很不错。











求职 : 技术/实施/服务顾问
论坛徽章:
5
授权会员
日期:2006-11-18 08:50:312013年新春福章
日期:2013-02-25 14:51:24ITPUB元老
日期:2013-03-01 14:33:22ITPUB社区千里马徽章
日期:2013-06-09 10:15:34奥运纪念徽章
日期:2013-06-18 09:13:52
2#
发表于 2013-7-8 13:23 | 只看该作者
沙发?

使用道具 举报

回复
论坛徽章:
0
3#
发表于 2013-7-8 14:12 | 只看该作者
请问你说的是那个版本的?

使用道具 举报

回复
论坛徽章:
70
夏利
日期:2013-09-29 21:02:15天蝎座
日期:2016-03-08 22:25:51嫦娥
日期:2014-03-04 16:46:45ITPUB年度最佳技术原创精华奖
日期:2014-03-04 16:19:29马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:11
4#
发表于 2013-7-8 14:36 | 只看该作者
michael-zheng 发表于 2013-7-8 14:12
请问你说的是那个版本的?

10G之后都是楼主所说的情况。

使用道具 举报

回复
论坛徽章:
70
夏利
日期:2013-09-29 21:02:15天蝎座
日期:2016-03-08 22:25:51嫦娥
日期:2014-03-04 16:46:45ITPUB年度最佳技术原创精华奖
日期:2014-03-04 16:19:29马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:11
5#
发表于 2013-7-8 14:37 | 只看该作者
我以前好像测过,buffer Pin lock不是enq锁机制,是随机的。

使用道具 举报

回复
论坛徽章:
21
奔驰
日期:2013-08-06 15:23:05日产
日期:2013-08-07 22:56:38蜘蛛蛋
日期:2012-12-29 19:15:08奥迪
日期:2013-08-07 17:02:24数据库板块每日发贴之星
日期:2010-06-28 01:01:03奥迪
日期:2013-08-13 10:10:28本田
日期:2013-11-20 15:17:02优秀写手
日期:2013-12-18 09:29:08玉兔
日期:2014-03-04 16:47:17铁扇公主
日期:2012-02-21 15:02:40
6#
 楼主| 发表于 2013-7-8 14:39 | 只看该作者
vage 发表于 2013-7-8 14:37
我以前好像测过,buffer Pin lock不是enq锁机制,是随机的。

呵呵,这个可以另外讨论。lewis的oracle core里提到的是,还是enq锁的机制。
The structure of a pin includes the address of the session holding, or waiting, on the pin. So when a
blocking holder removes their pin from the buffer header, they can post the waiter at the head of the
waiters’ list.

使用道具 举报

回复
招聘 : 数据库管理员
论坛徽章:
25
ITPUB9周年纪念徽章
日期:2010-10-08 09:32:25itpub13周年纪念徽章
日期:2014-10-08 16:34:19itpub13周年纪念徽章
日期:2014-10-10 17:49:05马上有车
日期:2014-12-19 09:23:24马上加薪
日期:2014-12-29 20:30:27马上有车
日期:2015-01-20 22:29:13美羊羊
日期:2015-03-04 14:52:282015年新春福章
日期:2015-03-06 11:58:18狮子座
日期:2015-07-14 14:44:11秀才
日期:2015-08-17 13:13:32
7#
发表于 2013-7-8 14:43 | 只看该作者
版本应该是10g以上的吧

读进程首先读取buffer,后来写进程来修改buffer,这个时候,读进程会被阻塞吗。


使用道具 举报

回复
论坛徽章:
70
夏利
日期:2013-09-29 21:02:15天蝎座
日期:2016-03-08 22:25:51嫦娥
日期:2014-03-04 16:46:45ITPUB年度最佳技术原创精华奖
日期:2014-03-04 16:19:29马上加薪
日期:2014-02-19 11:55:14马上有对象
日期:2014-02-19 11:55:14马上有钱
日期:2014-02-19 11:55:14马上有房
日期:2014-02-19 11:55:14马上有车
日期:2014-02-19 11:55:14马上有车
日期:2014-02-18 16:41:11
8#
发表于 2013-7-8 14:46 | 只看该作者
wei-xh 发表于 2013-7-8 14:39
呵呵,这个可以另外讨论。lewis的oracle core里提到的是,还是enq锁的机制。
The structure of a pin in ...

哪应该是Lewis错了。
我用mdb验证过。手动将独占的Buffer Pin加上(使用oradebug),然后搞几个查询,几个查询被阻塞后,用mdb将几个进程阻塞,再用Oradebug将buffer Pin去掉,看看谁会先得到Buffer Pin。

我记得已经没有前来后到了。

使用道具 举报

回复
论坛徽章:
21
奔驰
日期:2013-08-06 15:23:05日产
日期:2013-08-07 22:56:38蜘蛛蛋
日期:2012-12-29 19:15:08奥迪
日期:2013-08-07 17:02:24数据库板块每日发贴之星
日期:2010-06-28 01:01:03奥迪
日期:2013-08-13 10:10:28本田
日期:2013-11-20 15:17:02优秀写手
日期:2013-12-18 09:29:08玉兔
日期:2014-03-04 16:47:17铁扇公主
日期:2012-02-21 15:02:40
9#
 楼主| 发表于 2013-7-8 14:55 | 只看该作者
vage 发表于 2013-7-8 14:46
哪应该是Lewis错了。
我用mdb验证过。手动将独占的Buffer Pin加上(使用oradebug),然后搞几个查询, ...

恩,其实我觉得既然已经有了队列,那么实现这种先进先出的成本是很低的。波波研究研究,可以发出来看看

使用道具 举报

回复
论坛徽章:
5
复活蛋
日期:2012-11-02 16:27:37灰彻蛋
日期:2013-01-27 17:08:112013年新春福章
日期:2013-02-25 14:51:24复活蛋
日期:2013-05-27 15:29:10优秀写手
日期:2014-07-01 06:00:12
10#
发表于 2013-7-8 14:57 | 只看该作者
占个位。

使用道具 举报

回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

TOP技术积分榜 社区积分榜 徽章 团队 统计 知识索引树 积分竞拍 文本模式 帮助
  ITPUB首页 | ITPUB论坛 | 数据库技术 | 企业信息化 | 开发技术 | 微软技术 | 软件工程与项目管理 | IBM技术园地 | 行业纵向讨论 | IT招聘 | IT文档
  ChinaUnix | ChinaUnix博客 | ChinaUnix论坛
CopyRight 1999-2011 itpub.net All Right Reserved. 北京盛拓优讯信息技术有限公司版权所有 联系我们 未成年人举报专区 
京ICP备16024965号-8  北京市公安局海淀分局网监中心备案编号:11010802021510 广播电视节目制作经营许可证:编号(京)字第1149号
  
快速回复 返回顶部 返回列表