上篇文章,简单分析了 RedoLog 字节格式。接下来,介绍下解析过程中关键问题的处理,主要包括:如何计算 Record 头长度-VLD,如何计算事务的 XID,数值字节的转换逻辑。
1. 计算 Record 头长度-VLD
Record 头的长度是根据 VLD 的值得出的,已知的值有:
图中每个值的含义是:
- value= 0:Record 内容无效
- value= 1:Record 包含 change vectors
- value= 2:Record 包含已提交的 SCN
- value= 4:包含依赖 SCN,内部还有其他 SCN
- value= 8:Record 新的 SCN,此时,SCN 已在此实例的重做日志中精确分配
- value=16:Record 旧的 SCN,重做日志中,在此点或之前分配的 SCN。可能由另一个实例分配
- value=32:分配了新的 SCN,确保在按 SCN 排序时,某些块的重做由 inc/seq# 排序
Record 头长度有两种情况:24字节 和 68字节,通常情况下是 24字节,在某些情况下会增加到 68字节。
目前,没有找到明确的资料说明哪些是某些情况。能搜索到的是:只要 VLD 的值中包括了4,长度就是68字节,反之是24字节。包括 4的意思是,比如 VLD: 0x0d,十进制就是 13=8+4+1,长度就是 68;VLD: 0x09,十进制就是 9=8+1,长度就是 24。
仔细分析下这些值的组成,这个逻辑很容易使用位运算来处理!
2. 计算事务 XID
在日志里,事务的开始和结束标志,有可能在一个 Record 中,也有可能分散在不同的 Record 中,提交和回滚的是哪个事务,都是通过 XID 关联起来的。
有些操作能提取到 XID,有些则不能,但是每个操作都会有开始和结束,5.2 和 5.4 两个操作码,下面是这两个操作的 dump 信息:
CHANGE #2 TYP:0 CLS:19 AFN:3 DBA:0x00c00090 OBJ:4294967295 SCN:0x0000.0010c5bb SEQ:3 OP:5.2 ENC:0 RBL:0ktudh redo: slt: 0x0018 sqn: 0x0000033a flg: 0x0012 siz: 108 fbi: 0 uba: 0x00c007a0.009b.40 pxid: 0x0000.000.00000000CHANGE #4 TYP:0 CLS:19 AFN:3 DBA:0x00c00090 OBJ:4294967295 SCN:0x0000.0010c5e6 SEQ:1 OP:5.4 ENC:0 RBL:0ktucm redo: slt: 0x0018 sqn: 0x0000033a srt: 0 sta: 9 flg: 0x2 ktucf redo: uba: 0x00c007a0.009b.41 ext: 2 spc: 640 fbi: 0
XID 由三部分组成 xid: ..,slt 和 sqn 上面信息都有,主要是 USN 的值,它的全称是 Undo Segment Number,可根据 CLS 的值确定。
CLS 即 Block classes,已知的有:
值大于 16 的是保留给 undo segments (撤消段)使用,如图所示,每个 undo segment 都有编号,并且关联两个 CLS,一个用于 undo segment header (就是 5.2 使用);一个用于 undo segment blocks (就是 5.1 使用)。
根据此表对应关系,示例中 CLS:19 对应的 USN=2,所以此次事务的 XID 为 0x0002.018.0000033a,slt 为什么去掉个 0 暂不知原因。
3. 数值字节的转换
关于数值的存储,Oracle 有一个编码方法,先看下数字编码情况:
- 1-99,以2字节存储,第一个字节指示符为 0xC1,第二个字节为数值本身加1。比如,数字1 被编码为0xC102,2为0xC103
- 100-9999,存储为 2或3字节,指示符为 0xC2,数字100将是0xC202,而101将是0xC20202,201为0xC20302,301为0xc20402
- 10000-999999,指示符为 0xC3,数字10000将被存储为0xC302,10001被存储为0xC3020102
- 1000000-99999999,指示符为 0xC4,数字1000000将被存储为0xC402,1000001被存储为0xC402010102
- 100000000-9999999999,指示符为 0xC5 以此类推
规则就是:1到99之间的数字为单位数字;数值加1存储;数字末尾每多两个零,指示符加1。
比如 9999=99*100+99=0xC26464, 999999=99*10000+99*100+99=0xC3646464
上篇文章中的数字编码为 0xC20931 解码后 (0x09-1)*100+(0x31-1)=848。
4. ROWID 的计算
rowid 是一条记录的唯一标识,存储的是每条记录的实际物理地址。由 18位 64进制数据组成,包含四部分:
- 数据对象 ID:长度 6,data_object_id,日志中可直接获取
- 相对文件编号:长度 3,DBA 高10位
- 块编号:长度 6,DBA 低22位
- 行编号:长度 3,slot,这个获取有点麻烦,OP11.2 操作码存储在 KDO 结构中
rowid 的四部分采用 Base64 编码,即64进制,由 A~Z, a~z, 0~9, +, /共64个字符表示。其中 A表示0,B表示1,……,a表示26,……,0表示52,……,+表示62,/表示63。
来看一个插入示例:
CHANGE #2 TYP:0 CLS:1 AFN:4 DBA:0x01000213 OBJ:74770 SCN:0x0000.001307b0 SEQ:3 OP:11.2 ENC:0 RBL:0KTB Redo op: 0x02 ver: 0x01 compat bit: 4 (post-11) padding: 0op: C uba: 0x00c0041d.00ae.1bKDO Op code: IRP row dependencies Disabled xtype: XA flags: 0x00000000 bdba: 0x01000213 hdba: 0x01000212itli: 1 ispac: 0 maxfr: 4858tabn: 0 slot: 1(0x1) size/delt: 9fb: --H-FL-- lb: 0x1 cc: 2null: --col 0: [ 3] c2 64 64col 1: [ 1] 62
找出 rowid 的四部分并转成 Base64:
- object_id:74770=64*64*18+64*16+18,64进制是 AAASQS
- file_id:0x01000213 转成二进制后高10位是 100 十进制是4,64进制是 004=AAE
- block_id:0x01000213 转成二进制后低22位是 0000000000001000010011 十进制是 531=64*8+19,64进制是AAAAIT
- num:slot=0x1 64进制是 001=AAB
所以此次插入的记录rowid是 AAASQSAAEAAAAITAAB。
5. 总结
至此,通过这三篇文章,应该可以从 Redolog 文件中,准确的解析出每个 Record,从 Record 中解析出每个 Change,增删改操作的数据就在这些 Change 内。
默认情况下,INSERT 操作信息就记录在 OP 11.2 中;UPDATE 操作在日志中只是记录有变动的字段信息;DELETE 操作删除的记录信息记录在 undo OP 5.1 中。
在解析过程中还有很多问题要解决,比如一个 Record 中由多个新增删除更新操作,redo 块和 undo 块如何进行匹配?大字段 blob, clob 如何处理?回滚如何处理等等,总之完善起来那是相当麻烦的事~~
查看原文,请点击 了解更多