第一时间在amazon上买了Jonathan Lewis的新书,Oracle Core: Essential Internals for DBAs and Developers,经过漫长的等待,这本书在年前终于到了我的手上。趁着过年的这段时间,好好读了这本书,我喜欢在读书的时候做笔记,博客便是我做笔记、心得的一个地方。
说句题外话,在亚马逊上买书,纸质的书运费很贵,如果想在2周内到达,基本运费的价格要超过一本书的价格了,所以我们不得不选择比较慢的船运。不过,如果你有kindle或者iPad(装kindle app),那就又是另外一片天地了,你可以选择买电子的图书,价格还比纸质的书便宜,而且瞬间就能push到你的kindle或者iPad上了。所以,如果以后真的再有什么好书的话,我可能会选择买电子的书,而不再是傻傻的等纸书。电子的书可以很方便的进行搜索,单词翻译,做笔记,这都是纸质的书所做不到的事情,特别是翻译,当你遇到一个不认识的单词的时候,点一下就有该单词的翻译了,相当的方便。但是电子书也有不好的地方,特别是在iPad上,看书看个十几分钟,眼睛就酸疼的受不了了,还是纸质的书看起来舒服。
ok,我们言归正传,开始这本书的学习之旅。我不会涉及到这本书的所有方面,只是列出我认为重要的,或者我需要实验的地方,毕竟,纸上得来终觉浅,绝知此事要躬行。
第一章,get started
(1)读一致和scn和undo相关,而undo和transaction相关,transaction有和scn和ITL相关。
(2)有3种必要的表空间:undo tablespace,temporary tablespace和the rest
(3)kcmgss(get snapshot scn)->kcmgas(get and advance scn)
第二章,redo and undo
(1)记录产生的步骤:
(1.1) 创建redo向量,用来描述如何在undo block中插入一条undo记录
(1.2) 创建另一个redo向量,用来描述data block的变化
(1.3) 将上述2个变量合并成一个redo记录,写入到log buffer中
(1.4) 开始在undo block中插入undo记录
(1.5) 开始在data block中执行需要的变化。
我们来看看例子:
某table:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
sys@ORA8I(127.0.0.1)> desc test.t1 名称 空? 类型 ----------------------------------------------------- -------- ------------------------------- USERNAME NOT NULL VARCHAR2(30) USER_ID NOT NULL NUMBER ACCOUNT_STATUS NOT NULL VARCHAR2(32) LOCK_DATE DATE EXPIRY_DATE DATE DEFAULT_TABLESPACE NOT NULL VARCHAR2(30) TEMPORARY_TABLESPACE NOT NULL VARCHAR2(30) CREATED NOT NULL DATE INITIAL_RSRC_CONSUMER_GROUP VARCHAR2(30) EXTERNAL_NAME VARCHAR2(4000) sys@ORA8I(127.0.0.1)> sys@ORA8I(127.0.0.1)> select dbms_rowid.ROWID_RELATIVE_FNO(rowid) r_file_no, 2 dbms_rowid.ROWID_BLOCK_NUMBER(rowid) block_no, 3 dbms_rowid.ROWID_ROW_NUMBER(rowid) row_no, 4 rowid,username from test.t1; R_FILE_NO BLOCK_NO ROW_NO ROWID USERNAME ---------- ---------- ---------- ------------------ ------------------------------ 7 3 0 AAAAyWAAHAAAAADAAA TEST_1 7 4 0 AAAAyWAAHAAAAAEAAA TEST_2 7 4 1 AAAAyWAAHAAAAAEAAB TEST_3 7 4 2 AAAAyWAAHAAAAAEAAC TEST_4 7 4 3 AAAAyWAAHAAAAAEAAD TEST_5 7 4 4 AAAAyWAAHAAAAAEAAE TEST_6 7 4 5 AAAAyWAAHAAAAAEAAF TEST_7 7 4 6 AAAAyWAAHAAAAAEAAG TEST_8 |
做update前的block情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
alter system dump datafile 7 block 4; ##看其trace file中的row0,因为等会我们更新的会是这一行。 tab 0, row 0, @0xdae tl: 67 fb: --H-FL-- lb: 0x1 cc: 9 col 0: [ 6] 54 45 53 54 5f 32 col 1: [ 2] c1 03 col 2: [ 4] 4f 50 45 4e col 3: *NULL* col 4: *NULL* col 5: [ 8] 54 42 53 5f 54 45 53 54 col 6: [ 6] 53 59 53 54 45 4d col 7: [ 7] 78 70 01 0b 17 09 22 col 8: [22] 44 45 46 41 55 4c 54 5f 43 4f 4e 53 55 4d 45 52 5f 47 52 4f 55 50 |
做update:
1 2 3 4 5 6 |
sys@ORA8I(127.0.0.1)> update test.t1 set username='TEST_TEST_TEST_222222' where username='TEST_2'; 已更新 1 行。 已用时间: 00: 00: 00.31 sys@ORA8I(127.0.0.1)> |
看看该block的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
alter system dump datafile 7 block 4; tab 0, row 0, @0xbca tl: 82 fb: --H-FL-- lb: 0x1 cc: 9 col 0: [21] 54 45 53 54 5f 54 45 53 54 5f 54 45 53 54 5f 32 32 32 32 32 32 col 1: [ 2] c1 03 col 2: [ 4] 4f 50 45 4e col 3: *NULL* col 4: *NULL* col 5: [ 8] 54 42 53 5f 54 45 53 54 col 6: [ 6] 53 59 53 54 45 4d col 7: [ 7] 78 70 01 0b 17 09 22 col 8: [22] 44 45 46 41 55 4c 54 5f 43 4f 4e 53 55 4d 45 52 5f 47 52 4f 55 50 |
我们看到在block中,username的那一列,即col 0变了,从asc码的54 45 53 54 5f 32(6个字符,即TEST_2),变成了asc码的54 45 53 54 5f 54 45 53 54 5f 54 45 53 54 5f 32 32 32 32 32 32(21个字符,即TEST_TEST_TEST_222222)。
我们再来看看redo中的情况:
在update前后,进行dump redo:
1 2 3 4 5 6 7 8 9 10 11 12 |
sys@ORA8I(127.0.0.1)> select * from v$log; GROUP# THREAD# SEQUENCE# BYTES MEMBERS ARC STATUS FIRST_CHANGE# FIRST_TIME ---------- ---------- ---------- ---------- ---------- --- ---------------- ------------- ---------- 1 1 226 1048576 1 YES INACTIVE 264071 15-1月 -12 2 1 227 1048576 1 NO CURRENT 284166 23-1月 -12 3 1 225 1048576 1 YES INACTIVE 243893 11-1月 -12 已用时间: 00: 00: 00.00 sys@ORA8I(127.0.0.1)> ALTER SYSTEM DUMP LOGFILE 'E:\ORA8I\ORACLE\ORADATA\ORA8I\REDO02.LOG'; 系统已更改。 |
我们发现update之后的redo log中多了如下的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
REDO RECORD - Thread:1 RBA: 0x0000e3.00000018.0010 LEN: 0x0028 VLD: 0x01 SCN scn: 0x0000.0004561f 01/23/2012 18:10:08 CHANGE #1 TYP:0 CLS:15 AFN:2 DBA:0x00800402 SCN:0x0000.0004561a SEQ: 1 OP:14.1 ktecush redo: clear extent control lock REDO RECORD - Thread:1 RBA: 0x0000e3.00000018.0038 LEN: 0x0220 VLD: 0x01 SCN scn: 0x0000.0004561f 01/23/2012 18:10:08 CHANGE #1 TYP:0 CLS:15 AFN:2 DBA:0x00800402 SCN:0x0000.0004561f SEQ: 1 OP:5.2 ktudh redo: slt: 0x0016 sqn: 0x00000074 flg: 0x0412 siz: 132 fbi: 0 uba: 0x008004ca.0029.17 pxid: 0x0000.000.00000000 CHANGE #2 TYP:0 CLS:16 AFN:2 DBA:0x008004ca SCN:0x0000.00045619 SEQ: 1 OP:5.1 ktudb redo: siz: 132 spc: 1754 flg: 0x0012 seq: 0x0029 rec: 0x17 xid: 0x0002.016.00000074 ktubl redo: slt: 22 rci: 0 opc: 11.1 objn: 3222 objd: 3222 tsn: 6 Undo type: Regular undo Begin trans Last buffer split: No Temp Object: No Tablespace Undo: No 0x00000000 prev ctl uba: 0x008004ca.0029.16 prev ctl max cmt scn: 0x0000.00027f7f prev tx cmt scn: 0x0000.00027f80 KDO undo record: KTB Redo op: 0x04 ver: 0x01 op: L itl: scn: 0x0002.004.00000074 uba: 0x008004ca.0029.03 flg: C--- lkc: 0 scn: 0x0000.0003b93a KDO Op code: URP xtype: XA bdba: 0x01c00004 hdba: 0x01c00002 itli: 1 ispac: 0 maxfr: 2406 tabn: 0 slot: 0(0x0) flag: 0x2c lock: 0 ckix: 0 ncol: 9 nnew: 1 size: -15 col 0: [ 6] 54 45 53 54 5f 32 CHANGE #3 TYP:2 CLS: 1 AFN:7 DBA:0x01c00004 SCN:0x0000.0003b93a SEQ: 1 OP:11.5 KTB Redo op: 0x11 ver: 0x01 op: F xid: 0x0002.016.00000074 uba: 0x008004ca.0029.17 Block cleanout record, scn: 0x0000.0004561f ver: 0x01, entries follow... itli: 1 flg: 2 scn: 0x0000.0003b93a KDO Op code: URP xtype: XA bdba: 0x01c00004 hdba: 0x01c00002 itli: 1 ispac: 0 maxfr: 2406 tabn: 0 slot: 0(0x0) flag: 0x2c lock: 1 ckix: 0 ncol: 9 nnew: 1 size: 15 col 0: [21] 54 45 53 54 5f 54 45 53 54 5f 54 45 53 54 5f 32 32 32 32 32 32 CHANGE #4 MEDIA RECOVERY MARKER SCN:0x0000.00000000 SEQ: 0 OP:5.19 session number = 8 serial number = 10 current username = TEST login username = TEST client info = OS username = HEJIANMIN\Administrator Machine name = WORKGROUP\HEJIANMIN OS terminal = HEJIANMIN OS process id = 2092:7648 OS program name = SQLPLUS.EXE |
我看到change #2中有我们的更改前的记录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
CHANGE #2 TYP:0 CLS:16 AFN:2 DBA:0x008004ca SCN:0x0000.00045619 SEQ: 1 OP:5.1 ktudb redo: siz: 132 spc: 1754 flg: 0x0012 seq: 0x0029 rec: 0x17 xid: 0x0002.016.00000074 ktubl redo: slt: 22 rci: 0 opc: 11.1 objn: 3222 objd: 3222 tsn: 6 Undo type: Regular undo Begin trans Last buffer split: No Temp Object: No Tablespace Undo: No 0x00000000 prev ctl uba: 0x008004ca.0029.16 prev ctl max cmt scn: 0x0000.00027f7f prev tx cmt scn: 0x0000.00027f80 KDO undo record: KTB Redo op: 0x04 ver: 0x01 op: L itl: scn: 0x0002.004.00000074 uba: 0x008004ca.0029.03 flg: C--- lkc: 0 scn: 0x0000.0003b93a KDO Op code: URP xtype: XA bdba: 0x01c00004 hdba: 0x01c00002 itli: 1 ispac: 0 maxfr: 2406 tabn: 0 slot: 0(0x0) flag: 0x2c lock: 0 ckix: 0 ncol: 9 nnew: 1 size: -15 col 0: [ 6] 54 45 53 54 5f 32 |
change #3中有我们更改后的记录
1 2 3 4 5 6 7 8 9 10 11 12 |
CHANGE #3 TYP:2 CLS: 1 AFN:7 DBA:0x01c00004 SCN:0x0000.0003b93a SEQ: 1 OP:11.5 KTB Redo op: 0x11 ver: 0x01 op: F xid: 0x0002.016.00000074 uba: 0x008004ca.0029.17 Block cleanout record, scn: 0x0000.0004561f ver: 0x01, entries follow... itli: 1 flg: 2 scn: 0x0000.0003b93a KDO Op code: URP xtype: XA bdba: 0x01c00004 hdba: 0x01c00002 itli: 1 ispac: 0 maxfr: 2406 tabn: 0 slot: 0(0x0) flag: 0x2c lock: 1 ckix: 0 ncol: 9 nnew: 1 size: 15 col 0: [21] 54 45 53 54 5f 54 45 53 54 5f 54 45 53 54 5f 32 32 32 32 32 32 |
在这里,我们在redo中除了看到上述的数据变化之外,我们还能获得以下信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
(1)op code :URP, update row piece (2)update的block 地址,bdba:1c00004: sys@ORA8I(127.0.0.1)> select dbms_utility.data_block_address_file(29360132) as file_id, 2 dbms_utility.DATA_BLOCK_ADDRESS_BLOCK(29360132) as block_id from dual; FILE_ID BLOCK_ID ---------- ---------- 7 4 (3) update的block的segment header地址:hdba:1c00002: sys@ORA8I(127.0.0.1)> select dbms_utility.data_block_address_file(29360130) as file_id, 2 dbms_utility.DATA_BLOCK_ADDRESS_BLOCK(29360130) as block_id from dual; FILE_ID BLOCK_ID ---------- ---------- 7 2 (4)ITL槽位信息:itli: 1 (5)总共的行数:ncol: 9 (6)uba: 0x008004ca.0029.17,注意UBA的格式是DBA.seq#.rec#,我们根据file和block,能dump出对应undo中的信息: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
sys@ORA8I(127.0.0.1)> select dbms_utility.data_block_address_file(8389834) as file_id, 2 dbms_utility.DATA_BLOCK_ADDRESS_BLOCK(8389834) as block_id from dual; FILE_ID BLOCK_ID ---------- ---------- 2 1226 sys@ORA8I(127.0.0.1)> alter system dump datafile 2 block 1226; 系统已更改。 已用时间: 00: 00: 01.66 sys@ORA8I(127.0.0.1)> *----------------------------- * Rec #0x17 slt: 0x16 objn: 3222(0x00000c96) objd: 3222 tblspc: 6(0x00000006) * Layer: 11 (Row) opc: 1 rci 0x00 Undo type: Regular undo Begin trans Last buffer split: No Temp Object: No Tablespace Undo: No rdba: 0x00000000 *----------------------------- uba: 0x008004ca.0029.16 ctl max scn: 0x0000.00027f7f prv tx scn: 0x0000.00027f80 KDO undo record: KTB Redo op: 0x04 ver: 0x01 op: L itl: scn: 0x0002.004.00000074 uba: 0x008004ca.0029.03 flg: C--- lkc: 0 scn: 0x0000.0003b93a KDO Op code: URP xtype: XA bdba: 0x01c00004 hdba: 0x01c00002 itli: 1 ispac: 0 maxfr: 2406 tabn: 0 slot: 0(0x0) flag: 0x2c lock: 0 ckix: 0 ncol: 9 nnew: 1 size: -15 col 0: [ 6] 54 45 53 54 5f 32 |
注意这部分的undo的dump的内容,很像刚刚我们dump出来的redo中的change #2部分。而且change #2中第一行的DBA:0x008004ca,也即为undo block的地址。这也符合我们刚刚1.1~1.5的顺序,先有change #2,即1.1,你可以看到在redo中该change向量和undo block中是多么的类似,完成change #2之后,再做change #3,即block中的更改后的信息。
1 2 3 4 5 6 7 8 9 10 11 |
(2)事务的ACID原则:Atomicity原子性,undo有关;consistency一致性,undo有关;isolation隔离性,undo有关;durability持久性(可恢复性),redo有关。 (3)oracle提供3种隔离机制:read commit(默认),read only,serializable。 (4)redo的机制有多简单?——写后即忘,除非需要recover,或者stream,dg,cdc,一般实例不再会访问redo。 (5)redo的争用:在10g之前,只有一个redo log buffer,为了避免多个process写同一个redo log buffer,引入了latch,redo allocation latch。在10g之前,一个change=一个record=一个allocation。10g之后,引入private redo log buffer概念。 (6)10g中的写redo log buffer的步骤(注这里引入了10g的新特性,即IMU,in memory undo): (6.1)通过获得在private memory中的配对的内存结构,开始事务。配对的内存结构一个是从x$ktifp(即in memory undo pool,负责undo变化矢量),一个是从x$kcrfstrand(即private redo,负责forward变化矢量)。 (6.2)标记每个受影响的block为“has private redo”(但是不改变block) (6.3)将每个undo变化矢量写入被选中的in memory undo pool (6.4)将每个redo变化矢量写入被选中的private redo thread (6.5)通过将上述的2个内存结构串联到一个redo change记录中,事务完成。 (6.6)将redo change记录拷贝到redo中,并且将change apply到block上。 |
既然聊到了IMU,这里是传统的undo和IMU的一些比较。注意,不管是传统的undo机制还是IMU,oracle还是会产生一样多的redo向量,他们之间的差别,只是提交到logbuffer的次数。
举例:
1 2 3 4 5 6 7 |
t0时间:某表t,未进行更新。 t1时间:update t set a='B' where id=1; t2时间:update t set a='C' where id=1; t3时间:commit; |
那么传统的undo和redo机制为:
在t1:
1 2 3 4 5 6 7 8 9 |
datablock在block header分配ITL。 生成undo的change vector(即要写A) 生成data的change vector(即要从A变成B) 将undo的redo vector和data的redo vector合并成一条change record。 获得redo copy的latch 获得redo allocation的latch 将change record写入到log buffer中 应用undo的change vector(即写A) 应用data的change vector(即从A变B) |
在t2:
1 2 3 4 5 6 7 8 |
生成undo的change vector(即要从A变B) 生成data的change vector(即要从B变C) 将undo的redo vector和data的redo vector合并成一条change record。 获得redo copy的latch 获得redo allocation的latch 将change record写入到log buffer中 应用undo的change vector(即从A变B) 应用data的change vector(即从B变C) |
在t3:
1 2 |
更改undo segment header中的TX table状态。使得其他session可以看到更新后的数据。 commit触发LGWR写redo buffer到redo log。 |
关于commit是先更新TX table还是先Flush redo buffer,崔华这里有篇有意思的文章。
那么在IMU的情况下,又是怎么样的呢?
在t1:
1 2 3 |
datablock在block header分配ITL。 获取IMU latch,在shared pool中生成private undo,将undo的redo(即undo change vector)写入到private undo中。 获取redo allocation latch,在shared pool中生成private redo(即redo strand),将data的redo(即data change vector)写入其中。 |
在t2:
1 2 3 |
再次获取另外IMU latch,在shared pool中生成private undo,将undo的redo(即undo change vector,从A变B)写入到private undo中。 buffer cache中的ITL更新信息,指向新的undo change vector。 利用原来的redo allocation latch,在shared pool中生成private redo(即redo strand),将data的redo(即data change vector,从B变C)写入其中。 |
在t3,commit的时候(我们这里假设commit的时候,IMU也flush了)。
1 2 3 4 5 |
transaction以将两种内存结构(private redo和private undo)合并成一个单个的redo change record结束。 获取public redo allocation和redo copy的latch,将single redo change record写入redo buffer redo buffer写入redolog (flush IMU)a. 将IMU node的内容写入到undo block (flush IMU)b. 更改ITL的指针指向,原来是指向IMU node的,现在指向undo block |
【图一,合并以及写到redo buffer:】
【图二,写redo log以及flush IMU:】
关于是否存在private redo allocation latch和public redo allocation latch,我们可以找到所有redo allocation latch,将其用oradebug poke住,此时运行一个脚本来update和commit,发现会hung住,然后从gets的少数向多数一个一个的释放redo allocation latch,你会发现在倒数第三个时update能做了,但是commit不能做。因为commit需要public redo allocation latch,此时还是poke住的,我们是否第一个或第二个的时候,commit能完成了。因此,在10g的20个redo allocation latch中,18个是private的,2个是public的。
说个题外话,关于各个latch的parent latch数量和子latch数量,可以用以下sql检查。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
select d.kslldlvl latch_level, d.kslldnam type_name, sum(decode(l.kslltcnm, 0, 1, null)) parent, sum(decode(l.kslltcnm, 0, null, 1)) children from sys.x$kslld d, sys.x$ksllt l where d.inst_id = userenv('Instance') and l.inst_id = userenv('Instance') and l.kslltnum = d.indx group by d.kslldlvl, d.kslldnam order by d.kslldlvl, d.kslldnam; |
9i的redo allocation child latch是一个,而10g有20个。
如果说redo的特性是“即写即忘”,那么undo有2个特性:读一致和回滚。
读一致性主要针对某个块,找到这个块所连接的所有的undo记录(前映像)。
而回滚主要针对事务,需要找undo记录所在事务链表。
3条评论
拜读了
写的很深入!
你参与 oracle core的翻译了?
re yyp2009:不好意思,没参与。是老熊和其他的朋友在翻译。我只是读书笔记。