转载地址:http://www.itpub.net/thread-1803010-1-1.html
log buffer在Oracle的各个版本里一共发生过2次重大的改变:
1)多log buffer(redo strands)的出现(9I)
2)private redo和in memory undo的出现(10g)
你可能会补充说,还应该包括12c的多lgwr,是的,这也是一次革命性的改变,但是目前还没有对它有深刻的研究。我们暂时先把目光放回到11G之前。
ORACLE两次log buffer的重大改变,都跟redo allocation latch有着直接性的联系。本文以redo allocation latch切入点,描述log buffer的发展进化史。
由于题目写的比较大,不可能面面俱到,没有做太多细节性的描述。
redo allocation latch存在的问题
既然说是redo allocationlatch的问题,那么你可能会问,redo allocation latch出了什么问题,为什么要对它进行改进?
我来给大家一个简单的示例,一目了然的能知道一个小小的redo allocation latch存在什么严重的性能问题:
下面的代码里有一张wxh_tbd的表,表上无索引,我们更新了3条记录。下面的代码中,你只需要关注update的语句,其他的语句都是我采的快照,为了做分析用,你可以简单的把它忽略。
execute snap_latch.start_snap
execute snap_my_stats.start_snap
update wxh_tbd set object_name='t' where rownum<4;
execute snap_latch.end_snap
execute snap_my_stats.end_snap
---------------------------------
Latch waits:- 17-Jul 08:51:59
Interval:- 0 seconds
---------------------------------
Latch Gets Misses Sp_Get Sleeps Im_Gets Im_MissHolding Woken Time ms
----- ---- ------ ------ ------ ------- -------------- ----- -------
redo copy 0 0 0 0 3 0 0 0 .0
redo allocation 0 0 0 0 3 0 0 0 .0
Session stats - 17-Jul 09:11:39
Interval:- 0 seconds
---------------------------------
Name Value
---- -----
redo entries 3
redo size 1,180
非常容易看到,仅仅是修改了3条记录,ORACLE就申请了三次的redo copy latch,redo allocation latch。产生了3个redo entries,也就是3个redorecord。
不难分析出每产生一个redo record,oracle就要申请一次redo copylatch+redo allocation latch。
那什么时候会产生一个redo record呢?它由什么组成的呢?
先回答第一个问题:每修改数据块一次!就产生一个redo record。
一般情况下,一个redo recored由一个undorecord(用来描述undo的修改)和一个redochange vector(用来描述数据块的修改)组成,如下:
REDO RECORD - Thread:1 RBA: 0x00036f.00000005.008c LEN: 0x00f8 VLD: 0x01
SCN: 0x0000.03ee485a SUBSCN: 1 03/13/2011 17:43:01
CHANGE #1 TYP:0 CLS:36 AFN:2 DBA:0x0080009a SCN:0x0000.03ee485a SEQ: 4 OP:5.1---------undo record
…
CHANGE #2 TYP:0 CLS: 1 AFN:11 DBA:0x02c0018a SCN:0x0000.03ee485a SEQ: 2OP:11.5--------redo change vector
…
上面的dump记录里,OP:5.1代表的是undo 块的修改,OP:11.5代表对数据块的修改。还有更多的OP代码,大家可以去参照了解下。
我们的实验里,修改了三次数据块,产生了三个redo record。如果object_name有索引的话,redo copy latch,redo allocation latch的数字至少是9.因为索引的每次update相当于是delete+insert,索引部分至少会增加6次redo copy latch,redoallocation latch的申请。
ORACLE里对于redo copy latch的数量一般是比较多的,依据你的CPU数来决定,缺省是CPU数的两倍(通过隐含参数_log_simultaneous_copies控制)。因此造成竞争的几率很低。
但是对于redo allocation latch来说,在多redostrands出现之前,就只有一把。在一个交易频繁的系统里,势必会造成对redo allocationlatch的竞争。
Redo allocation latch有什么用?
毋庸置疑,log buffer是个共享的地方,有很多会话需要在这个空间里为自己的日志分配空间,必须要机制去保证保证会话间彼此的日志不互相覆盖。
ORACLE里是通过redo allocation latch做到的这一点。
看上面的图,我们设定了log buffer 由9个logbuffer block组成,典型的,一般一个log buffer block跟磁盘的扇区大小匹配,512字节。SSD之类的固态盘很多已经不是这个扇区大小了。
这里面有2个指针需要特别指出来,一个是start of free space,一个是end of free space。这两个指针的移动靠redo allocation latch去保护。
我们可以举个例子来,这样更容易理解:
session 1,session 2,session 3同时想要在log buffer里分配空间,为了简化我们的场景,我们假设每个SESSION计划分配512字节的空间,也就是刚好是一个log buffer block块的大小。
redo allocation latch被session 1抢到,假如当前的startof free space的位置在块1,那么session 1持有redo allocation latch后,移动start of free space指针到2。然后释放latch。
由于session2,session 3没有抢到唯一的一把redoallocation latch,它不能去log buffer 里预留空间,只能等待latch释放。
session 1释放latch后,假如session 2获得了,它继续移动start of free space到3,然后释放latch。
通过上面的机制,保证了多个session并发往log buffer分配空间的时候,不会导致日志覆盖。
你可能会问:你怎么没有提到end offree space?
好,我们来说说这个end of freespace什么时候会移动。
假如session 2的记录是一个commit record,那么它会触发lgwr写,lgwr刷新完日志后(我们的例子中,会把1,2两个buffer刷到磁盘),移动end of free space到3。
这个时候end of freespace和start of free space在位置上是重合的
最后session3获得了redo allocation latch,移动startof free space到4.
经过前面的描述,相信你基本上明白了redo allocatoin latch为什么面临性能问题:争用严重,以及它存在的价值:保护空间分配。
/************这一块的内容大家了解下就可以,稍微的有点深入*****************************************************************/
在我们真正去看9I出现的redo strands之前,我们可以再看一个稍微深入的实验。这个实验里,仅仅跟我们第一次的实验,多了一个commit
execute snap_latch.start_snap
update wxh_tbd set object_name='t' where rownum<4;
commit;
execute snap_latch.end_snap
---------------------------------
Latch waits:- 17-Jul 11:42:52
Interval:- 0 seconds
---------------------------------
Latch Gets Misses Sp_Get Sleeps Im_Gets Im_MissHolding Woken Time ms
----- ---- ------ ------ ------ ------- -------------- ----- -------
redo copy 0 0 0 0 4 0 0 0 .0
redo allocation 3 0 0 0 4 0 0 0 .0
仅仅多了一个commit,redo allocationlatch的申请的数量多了4次!!!!!
commit本身产生的redo record要申请一次redoallocation。+1
由于我的环境里有2个redo strands,因此提交的时候,lgwr要获得每个redo strand的redo allocation latch,lgwr此时获取redo allocation latch是为了start of free space指针到当前buffer block的结尾(日志写浪费就是这么产生的) +2
写完日志后,ORACLE要再次获取redoallocation latch,移动end of free space指针到当前写位置处。由于我只有一个redo strand有日志要写(另外一个是空的),所以申请一个redo strand的redo allocation latch。+1
/************这一块的内容大家了解下就可以,稍微的有点深入*****************************************************************/
9I 多log buffer的出现
ORACLE从9I起,有了一个新特性,允许使用多个redo strands,多个redo strands组成一个log buffer。每一个redo strand由一个redo allocation latch保护。这样的话,一定程度上增加的redo分配空间的扩展性。
但是多redo strands的出现,ORACLE需要解决一些棘手的问题,首先就是:事物日志的顺序问题
我们假设现在系统里存在2个redo strands。
1)首先考虑单个SESSION的情况
update xxx set name=1 where rowid=xxx;
update xxx set name=2 where rowid=xxx;
对于同样的rowid,一个事物内修改了2次,最终的结果是name=2。
如果name=1的日志拷贝到了redo strand2,name=2的日志拷贝到了redo strand1。
lgwr刷新日志后,真正写到log里的日志顺序,可能是先更新name=2,再更新name=1。
这样就会导致最终的数据不正确。
如何解决?
ORACLE可以通过会话与固定的redo strand做绑定,保证一个会话产生的redo一定在一个redo strand里就可以了,这点我相信不难做到。
2)我们再看看多个session的情况。
SESSION 1:
update xxx set name=1 where rowid=xxx;
SESSION 2:
update xxx set name=1 where rowid=xxx;
貌似依然会出现我们情况一里的那种问题。但是,但是,真的不会出现。(后面我会又否认不会出现,慢慢看吧)
SESSION1更新成功后,如果不提交,SESSION 2的会话被等待锁,不会产生日志。
如果session 2能够更新成功了,说明SESSION 1一定提交了,提交一定代表日志已经在SESSION 2前持久化到磁盘了。所以,这种情况,SESSION 2的日志一定是落后于SESSION 1到LOG里的。
因此有了ORACLE里的锁做保障,我们可以对这种情况高枕无忧了。
但是?我又要说但是了,ORACLE里存在一些ACID的异常。相信很多人知道这个特性了,但是并不知道,它跟这里说的日志顺序有什么关系。
如果你对于ORACLE的ACID已经非常了解了,下面的一块内容,你直接跳过。
什么是ACID异常?
首先我们需要了解,commit到底做了什么?
事物开始的时候,会先在回滚段段头(undo segment header)的事物表内申请一个slot,并在这个slot内记录下此事物的一些信息:比如事物的状态:活跃/提交,事物当前用的最后一个undo块是哪个(回滚的起点),一共使用了多少undo块等等。
事物结束的时候,也还会在这个slot上登记这个事物已经提交了,并且产生一个commit recored,这个commit recored说白了就是用来描述回滚段头这个slot的改变,最最重要的是描述了事物已经从活跃变为了提交。
事物表的slot一旦标记为提交,也就意味着别的会话能够看到此事物所作的所有修改了。
我们来列下commit后,触发的操作:
1)产生一个redo record去描述对于事物表slot的修改
2)拷贝这个redo record到log buffer
3)应用这个redo recored到对应的undosegment header
4)通知lgwr去写日志
我们要知道,我们在步骤3的时候,别的会话就认为这个事物已经提交了。但是这个时候,日志是还没持久化到磁盘的,会话仅仅是通知了LGWR去写。
步骤3和步骤4之间是有时间差的,虽然这个时间差很小!
OK,我们知道了什么是ACID异常,我们继续我们的问题:
我们再看看多个session的情况。
SESSION 1:
update xxx set name=1 where rowid=xxx;
SESSION 2:
update xxx set name=1 where rowid=xxx;
假如session 1提交了,通知lgwr去写了,但是lgwr在没真正开始工作之前,session 2已经看到session 1释放了锁,它可以产生它的事物日志往redo buffer里拷贝了。
由于ORACLE的GROUP COMMIT特性,LGWR每次都会尽可能多的拷贝BUFFER里的数据。很有可能session 2,session 1的LOG都同时刷新到了磁盘里,而且很可能是乱序的。
世界再一次变乱了,因为ORACLE的锁机制遇到ACID异常后,失灵了!
因此我认为ORACLE在恢复阶段,必须要对日志进行排序来规避这个问题。这里我们并不去探讨这个问题。
我们接着聊我们的主题:redo allocation latch的问题(有时候路走的太远了,就忘记了我们当初为什么出发)。
虽然我们看到ORACLE引入多redo strands后,似乎缓解了redo allocation latch的争用问题。但是解决的不够彻底。仅仅是对redoallocation latch做了非常有限的扩展。很容易看到,大多数的系统里默认的这个strands的数量是2,仅仅是提升了2倍的扩展性。当然这个值你可以设定,但是并不是越大越好。原因很复杂,看情况,可以再写一写这个方面的博客。
10G private redo的出现
如果我们能把一个事物产生的redo一次性的拷贝到log buffer(一次事务只申请一次redo allocation latch),而不是之前的one change=one allocation latch,那么就能极大程度的缓解了redoallocation latch竞争的问题。
ORACLE 10G后引入了private redo和inmemory undo,解决了这个问题,之所以说的是解决,而不是彻底解决,是因为新机制的限制比较多。
/************新机制有哪些限制?*****************************************************************/
在你打开辅助日志、FLASHBACKDATABASE特性、事务过大(超过了pool的大小)、RAC,都将不能使用这一特性
/************新机制有哪些限制?*****************************************************************/
private redo,in memoryundo本质上就是在内存里开辟出一块区域用来临时存放会话的私有日志,等提交的时候,一次性的刷入到公共的log buffer里。
这些私有区域由许多的private redo pool,in memory undo pool组成,在64位系统上,每个pool都接近128K的大小,这些pool用来临时存放会话的日志:数据块的redo放入private redo pool中,undo块的redo放入inmemory undo pool中。
在private redo和in memory undo机制下,我们文章刚开始的一个update流程,可以用如下顺序表示:
update wxh_tbd set object_name='t' where rownum<4;
1)首先获取一对私有的memory pool:privateredo pool,in memory undo pool
2)标记每一个修改的块有private redo
3)把产生的undo change vector写入inmemory undo pool
4)把产生的redo change vector写入privateredo pool
5)事物提交的时候,把2个pool里的change vector结合成一个大的redo record
6)拷贝这个record到公有的redo buffer里,并且应用这个record到对应的数据块和undo块。
从上面的流程描述可以知道:
1)一个事物的所有的change vector在提交的时刻从私有memory结合成一个大的redo record拷贝到公有的redo buffer里,只需要申请一次redo allocation latch。
大大的降低了redo allocation latch的申请数。
2)数据块、undo块只有在最后事务提交的时刻,才去应用日志改变。
其实在上面的描述中,为了简化,故意漏掉了一些细节:
1)每个private redo pool是由私有的redoallocation latch来保护的,每次事务期间,会话只需要申请一次私有的redo allocationlatch就可以了,个人认为ORACLE将会话与私有redopool做了绑定,比如在PGA里留一个指针或者描述符来指向这个redopool。会话获得私有redo allocation latch后,标记一下这个私有pool已被使用,告诉别的会话不要再来使用这个pool。
2)每个in memory undo pool是由inmemory undo latch来保护的,对于这个latch的申请要频繁一些,每一条undo record都要申请一次in memory undo latch。貌似ORACLE仅仅是把竞争从redo allocation latch变为了in memory undo latch的竞争,但是事实并不是这样,因为inmemory undo latch的数量是很多的,每个pool都对应了一个in memory undo latch。
3)我的描述中说,直到事务提交,否则ORACLE不会去应用日志到任何的数据块和UNDO块。但是对于UNDO段头的事物表是个特殊,每次事务开始前,都必须要修改这个slot。
我们可以重新梳理一下update流程:
1)首先获取一对私有的memory pool:privateredo pool,in memory undo pool。private redo pool的使用要获取私有的redo allocation latch,获取后,标记此pool被使用,释放latch。in memory undo pool的使用要获取in memory undo latch,每次有undo change vector产生都要持有in memory undo latch。
2)在undo段头的slot中,记录事务已经开始,以及相关信息
3)标记每一个修改的块有private redo(但是并不真正的修改数据块)
3)把产生的undo change vector写入inmemory undo pool
4)把产生的redo change vector写入privateredo pool
5)事物提交的时候,把2个pool里的change vector结合成一个大的redo record
6)拷贝这个record到公有的redo buffer里,并且应用这个record到对应的数据块和undo块。
虽然文章很长,但是由于牵扯的知识面较广,限于文章篇幅,有些细节并没有做过多的描述。里面很多的内容都可以单独再拉出来讨论。
QQ:252803295
技术交流QQ群:
DSI&Core Search Ⅰ 群:127149411(2000人技术群:未满)
DSI&Core Search Ⅱ 群:177089463(1000人技术群:未满)
DSI&Core Search Ⅲ 群:284596437(500人技术群:未满)
DSI&Core Search Ⅳ 群:192136702(500人技术群:未满)
DSI&Core Search Ⅴ 群:285030382(500人闲聊群:未满)
MAIL:dbathink@hotmail.com
BLOG: http://blog.csdn.net/guoyjoe
WEIBO:http://weibo.com/guoyJoe0218
ITPUB: http://www.itpub.net/space-uid-28460966.html
OCM: http://education.oracle.com/education/otn/YGuo.HTM