Oracle redo解析之-一、oracle redo log结构计算

做者啰嗦

  有疑问或合做能够联系:zhou_bihui@qq.com.
  demo程序移步:github/demo ,redo log分析的几个阶段以下(Oracle 11g R2):git

  1. redo结构计算(已更新)
  2. BBED & DUMP 工具的使用(已更新)
  3. 常见change的分析(已更新)
  4. rowid计算(已更新)
  5. Oracle经常使用数据类型的底层存储与转换
  6. redo怎么区分事务
  7. IMU模式与非IMU模式下redo record的不一样
  8. undo segment、undo block和data block的联系
  9. 完整事务计算,并根据事务构建sql
  10. DDL语句产生的redo浅析

经验之谈

  若是是想最后能构建出SQL语句,应该按如下步骤:
  1. redo log文件的结构能区分,即计算redo record,从redo record中计算出change,经过大量的archive log文件作大量的测试。
  2. 计算每一个change,先能构建简单的增删改,这里先不要考虑事务。
  3. 分析事务,这里须要判断事务的commit和rollbak,构建的话只须要构建commit的事务。
  4. 以后也能够继续学习构建DDL语句,也能够尝试作些小demo,例如保持两台oracle的数据一致,能够经过解析主机的redo log,将sql传到备机执行。github

前言

  要从oracle redo日志中反编译出sql,有如下3个步骤:
  1. 从redo log中将redo block header、redo record、redo record header、redo change、redo change header分离出来。
  2. 根据每个redo change中的opcode肯定change的操做类型。
  3. 根据xid区分事务。
sql

redo文件结构

block 0的数据格式

  block 0是redo的第一个数据块,记录了块大小,块总数等信息。须要注意的是,block不包含redo block header(即块头),不包含在块总数中。数据库

typedef struct file_header_0{
	uint32_t unknown0[5];
	unit32_t blocksize;  //块大小,512/1024...
	unit32_t blockcount;  //当前文件的总块数
	unit32_t unknown1[2];
	unit32_t zero[119];
}Redo_fh0
复制代码

block1的数据格式

  第2块作为数据库头,包含数据库信息(如版本号、数据库ID、文件序号等)。bash

tyoedef struct redo_block_header{
	uint32_t signature;  //签名
	unit32_t blocknum;   //块号
	unit32_t sequence;   //顺序号
	unit16_t offset;     //当前块的第一个redo record开始的位置,最高位舍弃
	unit16_t checksum;   //块的checksum,写入时更新
}Redo_bh

typedef struct RBA{  //redo文件中的位置信息
	unit32_t sequence;
	unit32_t blocknum;
	unit16_t offset;
}Redo_RBA

typedef struct scn{
	unit32_t scnbase;
	unit16_t scnwrapper;
}Redo_scn

typedef struct file_header_1{
	Redo_bh blockheader;
	unit32_t unknown0;
	unit32_t Compatibility Vsn;
	unit32_t db id;
	unit64_t db name;  //猜想用来表示数据库名称
	unit32_t control seq;
	unit32_t file size;
	unit32_t blksize;
	unit16_t file number;
	unit16_t file type;
	unit32_t activation id;
	unit8_t zero[36];
	unit8_t unknown1[64];
	unit32_t nab;  //当前文件最后一个有真实记录块的下一个块
	unit32_t resetlogs count;
	Redo_scn resetlos scn;
	unit16_t 0;
	unit32_t hws;
	unit32_t thread;
	Redo_scn low scn;
	unit16_t 0;
	unit32_t low scn time;
	Redo_scn next scn;
	unit16_t 0;
	unit32_t nex scn time;
	unit32_t unknown2;
	Redo_scn enabled scn;
	unit16_t 0;
	unit32_t enabled scn time;
	Redo_scn thread closed scn;
	unit16_t 0;
	unit32_t thread closed scn time;
	unit8_t unknown3[52];
	Redo_scn prev resetlogs scn;
	unit16_t 0;
	unit32_t prev resetlogs count;
	unit8_t unknown4[216]
}Redo_fh1
复制代码

block2的数据格式

  从第三块(block 2)开始,块里面存储着Oracle的redo日志。由块头和块体构成,其中块头即结构体Redo_bh,和block1的块头结构一致。oracle

tyoedef struct redo_block_header{
	uint32_t signature;  //签名
	unit32_t blocknum;   //块号
	unit32_t sequence;   //顺序号
	unit16_t offset;     //当前块的第一个redo record开始的位置,最高位舍弃
	unit16_t checksum;   //块的checksum,写入时更新
}Redo_bh

typedef struct block{
	Redo_bh blockheader;
	unit8_t redo record[496];  //redo record,日志记录
}
复制代码

  块头分析:
  1. signature -- 表示这是一个redo block。
  2. block number -- 当前块的编号。
  3. sequence -- 顺序号(序列号),即v$log视图的SEQUENCE#字段
  4. offset -- 标记本快中第一个redo record开始的位置,位置是包括块头的,且须要过滤最高位。
  5. checksum -- 校验值,块写入时计算获得。app

  操做记录(redo record)的结构:
  1. 一个记录头(redo record header)
  2. 多个change。工具

redo record header

  record header的长度有24字节和68字节两种状况。学习

typedef struct record_header0{
	unit32_t len;  //redo record的长度
	unit8_t vld;
	unit8_t unknown0;
	unit16_t record header scn wrapper;
	unit32_t record header scn base;
	unit16_t subscn; 
}

typedef struct record_header1{
	unit8_t unknown2[10]:
}Redo_rh24  //长度为24字节的record header结构

typedef struct record_header2{
	unit8_t unknown0[50];
	unit32_t timestamp;  //本次操做的时间戳
}Redo_rh68  //长度为68字节的record header结构
复制代码

  如何肯定record header的长度是24字节仍是68字节?
  通过屡次测试以及查找大神的文档,得出一个结论,且这个结论至今还未出错。在一位大神的文档中看到说record header的长度由vld决定,在11G版本下,我测试发现,只要vld的值中包括了4,长度就是68字节,反之是24字节。包括4的意思是,假设vld是12,4+8=12,说明vld包括了4,因此长度是68字节。vld的取值由如下表格肯定:测试

Mnemonic Value Description
KCRVOID 0 The contents are not valid.
KCRVALID 1 Includes change vectors
KCRDEPND 2 Includes commit SCN
KCRVOID 4 Includes dependent SCN
KCRNMARK 8 New SCN mark record. SCN allocated exactly at this point in the redo log by this instance
KCRMARK 16 Old SCN mark record. SCN allocated at or before this point in the redo. May be allocated by another instance
KCRORDER 32 New SCN was allocated to ensure redo for some block would be ordered by inc/seq# when redo sorted by SCN

  表格的含义我不太清楚,得出的结论是我屡次验证的来的。

redo change

  记录头以后就是一组redo change了,一个操做对应一个change,好比插入,更新,块清除等。每一个change对应一个操做码(opcode)。
  每一个change又分为change header,change length list(change向量表),和change body。

redo change header

typedef struct opcode{
	unit8_t layer number;  
	unit8_t code;  
}Redo_opcode
typedef struct change header{
	Redo_opcode opcode;
	unit16_t CLS;
	unit16_t AFN;
	unit16_t OBJ0;
	unit32_t DBA;
	unit32_t change header base;
	unit16_t change header wrapper;
	unit16_t unknown0;
	unit8_t SEQ;
	unit8_t TYP;
	unit16_t OBJ1;
}Redo_ch  //长度固定为24字节
复制代码

  计算OBJ(这里的OBJ是data_object_id,不是object_id):OBJ1 | (OBJ0 << 16)

例如:OBJ0=0x0001, OBJ1=0x0388  
     OBJ0左移16位:00000000 00000001 00000000 00000000
     OBJ1:00000011 10001000
     或的结果:00000000 00000001 00000011 10001000
     转成十进制:66440
OBJ为本次操做的表的data_object_id,能够根据OBJ从数据字典中查到表名,字段等信息用来构建SQL语句。
复制代码

redo change length list

  redo change header后是一个change向量表,主要是用来计算change的长度。change向量表的长度按4字节对齐,而后内部是每2字节一段。第1,2字节表示向量表总长度(未进行4字节对齐以前的有效长度),总长度以后剩余的长度(有效总长度-2),分为每2字节一段,每一段表示一个change部分的长度,这些长度会按顺序写在向量表以后,向量表中的长度为change部分的有效长度。
  向量表是变长的,和redo record同样,长度在最开始,所以每次都应该先读取表示有效长度的2字节进行解析。

例:change length list: 0a004800 31000800 0100c000
	1. 向量表总长度:0x000a = 10,有效长度是10,补齐4字节后是12,所以最后的2字节c000是无效字节
	2. 有效长度减去1,2字节后剩8字节,每2字节为一段,说明这个change被分红了4部分
	3. 第一部分有效长度: 0x0048 = 72, 第二部分有效长度: 0x0031 = 49, 第三部分有效长度: 0x0008 = 8, 第四部分有效长度: 0x0001 = 1
	4. 第一部分实际长度: 72字节, 第二部分实际长度: 52字节, 第三部分实际长度: 8字节, 第四部分实际长度: 4字节
	5. 整个change的实际长度: 24(header) + 12(length list) + 72 + 52 + 8 + 4 = 172字节
复制代码

计算

[注意]本机是小端机器,高字节在前,低字节在后。
Block2:
	1. offset:0x8010,过滤最高位,0x0010=16,表示Block2的第一个redo record从16字节开始。
	2. record len:0x00000100 = 256,说明record的长度为256.
	3. vld:在record的偏移量是4字节,vld自己的长度是1字节,从Block2中能够获得vld=0x05=5(1+4),说明当前record的record header长度为68字节。
	4. 68字节后,是record的第一个change[即6行的05021900处],change header固定为24字节。
	5. 24字节后,是change length list,即:04002000,当前change只有一个部分,长度为0x0020=32字节。
	6. 计算获得,第一个change的长度为:24(change header)+4(length list)+32=60字节。
	7. 从第6行的05021900处开始数60字节,接着的是第二个change,即8行的05011a00处。
	8. 24字节后是第二个change的length list:06001400 4c000000,length list的有效长度是6字节,当前change被分红了两部分,第一部分长度0x0014=20字节,第二部分长度=0x004c=76字节。
	9. 计算获得,第二个change的长度为:24+8(补齐4字节的倍数)+20+76=128字节。
	10. 到这里,第一个record结束了,接着是第二个record。
	11. 第二个record,从12行的3c000000处开始,这里回到第2步,从计算record len开始,同理。

Block3:
	1. 0ffset:0x8040,过滤最高位0x0040=64,说明Block3的第一个record开始于64字节处,而16字节-64字节之间的内容属于上一个Block的跨块record记录。
	2. 日后的计算,同Block2.

复制代码
相关文章
相关标签/搜索