oracle 11g歸檔日志研究_4,oracle11g歸檔_4
change的內容,是oracle日志轉化為SQL語句的核心,也是最麻煩,變化最多的地方。
先說opcode,opcode的含義網上隨便一搜有很多,真正對我有用的,只有增刪改,至於什麼搜索、索引等操作,我根本就不關心。
5.1:包含信息較多,每個增刪改一定對應一個有效的5.1,這個5.1中將包含原始數據,用來在回滾(undo)時使用。另外還會有大量的5.1操作,目前我並不理解其他5.1操作的含義,並將這些我認為“無效”的5.1忽略。5.1的數據內容按照如下順序排列:

![]()
typedef struct ktudb {
uint16_t siz;
uint16_t spc;
uint16_t flg;
uint16_t unknown0;
uint16_t xid0;
uint16_t xid1;
uint32_t xid2;
uint16_t seq;
uint8_t rec;
uint8_t ufo; //0x84: ktubu; 0x00: ktubl
}Redo_ktudb;
Redo_ktudb
這是5.1的change向量表之後的第一段數據,其中的xid也許可以作為txn的id,其他值我並不知道有什麼用。

![]()
typedef struct ktubl {
uint32_t objn;
uint32_t objd;
uint32_t tsn; //maybe
uint32_t noname; //maybe
uint8_t opc0;
uint8_t opc1;
uint16_t slt;
uint32_t unknown1;
uint32_t unknown0; //00 00 00 00
uint32_t uba0;
uint16_t uba1;
uint8_t uba2;
uint8_t unknown2; //00
uint32_t max_scn1;
uint16_t max_scn0;
uint16_t unknown3;
uint32_t tx_scn1;
uint16_t tx_scn0;
uint16_t unknown4;
uint32_t unknown5; //00 00 00 00
uint32_t txn_scn1; //位置不確定
uint16_t txn_scn0;
uint16_t unknown6; //00 00
uint32_t brb;
uint32_t unknown7; //00 00 00 00
uint8_t user;
uint8_t bcl; //maybe
uint8_t idx; //maybe
uint8_t flg2; //maybe
}Redo_ktubl;
Redo_ktubl
這是第二段,其中opc0.opc1應該為11.1,以對應我們的增刪改操作,其他值被我忽略。objd被我當做obj,在數據字典中查找對應項。雖然ktubl的結構被我定義成這樣,但實際數據是變長的,其可能只有24字節,故可能使用時只有前幾個元素的值才是真實的。

![]()
typedef struct KTB {
uint8_t op0;
uint8_t unknown0[7];
uint16_t xid0;
uint16_t xid1;
uint32_t xid2;
uint32_t uba0;
uint16_t uba1;
uint8_t uba2;
uint8_t unknown1;
uint16_t unknown2;
uint16_t scn0;
uint32_t scn1;
}Redo_KTB;
Redo_KTB
本段也是變長的,事實上很多操作都會有KTB段,但是它們的結構卻不相同。

![]()
typedef struct KDO {
uint32_t bdba;
uint32_t hdba;
uint16_t maxfr;
uint8_t unop0;
uint8_t unop1; //maybe
uint8_t itli;
uint8_t unknown1[3];
uint8_t slot;
uint8_t unknown2[3];
}Redo_KDO;
Redo_KDO
本段也是變長的,好吧,看來5.1裡面沒有什麼是固定的了。
在這4段之後的數據,就屬於undo段了,其數據擺放格式都差不多,可以按照向量表將其內容打印分析。
5.2:包含的信息較少,可能應該作為一個事務(txn)的開始,但目前被我忽略。

![]()
typedef struct ktudh {
uint16_t xid1; //slt
uint16_t unknown0;
uint32_t xid2; //sqn
uint32_t uba0;
uint16_t uba1;
uint8_t uba2;
uint8_t unknown1;
uint16_t flg;
uint16_t siz;
uint8_t unknown3[12];
}Redo_ktudh;
Redo_ktudh
這似乎是5.2包含的唯一一段數據,固定為32個字節,似乎5.2應該包含xid作為一個txn的標示,但它只包含了xid的一部分。
5.4:表示一個事務(txn)的結束,應該是意味著用戶提交了一次commit,可能應該與5.2對應。

![]()
typedef struct ktucm {
uint16_t slt;
uint16_t unknown0;
uint32_t sqn;
uint32_t srt; //長度1-4
uint32_t sta; //長度1-4
uint32_t flg; //長度1-4
}Redo_ktucm;
Redo_ktucm
5.4的第一段數據,固定為20字節。

![]()
typedef struct ktucf {
uint32_t uba0;
uint16_t uba1;
uint8_t uba2;
uint8_t unknown0; //00
uint16_t ext;
uint16_t spc;
uint16_t unknown1;
uint16_t fbi; //maybe
}Redo_ktucf;
Redo_ktucf
5.4的第二段數據,固定為16字節。
之後5.4可能還會有其他數據,我並不知道那是什麼,並且,似乎最後一段總是以4字節數據結束。
11.2:插入,對應insert語句。
11.2的第一段數據是KTB,其結構可以使用5.1的,但是長度卻不同,可能比5.1的要短。

![]()
typedef struct KDO_11_2 {
uint32_t bdba;
uint32_t hdba;
uint16_t maxfr;
uint16_t unknown0;
uint8_t itli;
uint8_t unknown1[3];
uint16_t unknown2;
uint16_t cc;
uint8_t unknown3[20];
uint16_t size_delt;
uint8_t slot;
uint8_t unknown4[10];
}Redo_KDO112;
Redo_KDO112
第二段是11.2的KDO。
從第三段開始,就是insert的內容了,每一段對應一個字段,對於insert語句來說,所有的字段都會在11.2中列出。
11.3:刪除,對應delete語句。

![]()
typedef struct KTB_11_3 {
uint8_t op0;
uint8_t unknown0[7];
uint16_t xid0;
uint16_t xid1;
uint32_t xid2;
uint32_t uba0;
uint16_t uba1;
uint8_t uba2;
uint8_t unknown1;
uint32_t zero0;
uint32_t zero1;
uint32_t zero2;
uint32_t zero3;
uint32_t zero4;
uint32_t unknown2;
uint32_t unknown3;
uint32_t unknown4;
uint16_t unknown5;
uint16_t scn0;
uint32_t scn1;
}Redo_KTB113;
Redo_KTB113
第一段,雖然名字都叫KTB,內容卻不一樣。
第二段是KDO,可以使用5.1的KDO結構。
11.5:修改,對應update語句。

![]()
typedef struct KTB_11_5 {
uint8_t op0;
uint8_t unknown0[7];
uint32_t uba0;
uint16_t uba1;
uint8_t uba2;
uint8_t unknown1;
}Redo_KTB115;
Redo_KTB115
第一段,又是一只獨特的KTB。

![]()
typedef struct KDO_11_5 {
uint32_t bdba;
uint32_t hdba;
uint16_t maxfr;
uint16_t unknown0;
uint8_t itli;
uint8_t unknown1[3];
uint8_t flag;
uint8_t lock;
uint8_t unknown2[2];
uint8_t slot;
uint8_t size; //maybe
uint8_t ncol;
uint8_t nnew;
uint8_t unknown4[5];
}Redo_KDO115;
Redo_KDO115
以及獨特的KDO。
第三段開始是update的數據,第三段本身,是類似於向量表的一個數組,只是它其中存儲的是要修改的數據字段id-1。
11.17:對應於有LOB字段的增、改操作,用來表示LOB字段的總長度。還有一種不包含LOB長度的11.17,其意義不明,被我忽略。

![]()
typedef struct LOB_11_17 {
uint16_t xid0;
uint16_t xid1;
uint32_t xid2;
uint32_t OBJ;
uint32_t unknown2;
uint32_t unknown3; //00
uint32_t unknown4; //00
uint32_t lobsize;
uint32_t unknown5; //00
}Redo_LOB_11_17;
Redo_LOB_11_17
這是11.17的第三段,前兩段被我忽略掉了,我並不知道它們是什麼。並不是所有11.17都有這個結構,所以需要進行判斷,如果此段的長度不是32字節,則認為這個11.17不是我們需要的。本段中lobsize就是本次操作的LOB字段的總長度。
19.1:對應於有LOB字段的增、改操作,存儲著LOB字段的內容,目前我所操作的oracle 11g,會將LOB切割成8168-36(LOB頭)=8132字節的塊,每塊分別放在一個19.1中。LOB的總長度需要通過11.17獲取,然後通過19.1獲取LOB數據。LOB頭格式:

![]()
typedef struct LOB_19_1_1 {
uint32_t unknown0;
uint32_t unknown1;
uint16_t unknown2;
uint32_t lob_set;
uint16_t unknown3;
uint32_t seq; //2 or 4 byte, lob編號, 1 to n
uint32_t unknown5; //00
uint32_t subseq; //2 or 4 byte, lob分段編號, 0 to n
uint32_t unknown7;
uint32_t unknown8; //00
}Redo_LOB_19_1;
Redo_LOB_19_1
LOB頭中有2個編號,這兩個編號表示LOB的排列順序,而且,由於insert和update使用的方式不同(java腳本、sqldeveloper等),LOB數據可能有重復(此時LOB第1個編號會+1,第2個編號會從0開始)。
以上是增刪改對應的基本數據結構。以下進行簡單總結說明:
我們進行的是增刪改操作,即11.2、11.3、11.5是我們真正操作的內容。
如果我們的增、改操作帶有LOB字段,則會出現11.17和19.1。
每個操作(指的是11.2、11.3、11.5),一定有其對應的5.1,其中含有undo所需的內容。
11.3最簡單,因為其中沒有需要解析的數據,只要通過5.1的undo段確定所在行即可,而通常只需要一個主鍵就解決了。
11.2中等,需要插入的數據都在11.2的數據段,其5.1的undo段是空的,因為插入的undo就是刪除。
11.5最復雜,需要修改的數據在11.5的數據段,而需要定位的行則必須從其對應的5.1的undo段解析,undo段存的其實就是11.5中對應字段的原值。
程序運行示例:
程序指定數據字典為dictionary.ora,歸檔日志文件為o1_mf_1_3279_brn6w2fm_.arc,程序運行結果非常巨大,只貼出其中一小部分。

解析數據字典,得出用戶名、表名、字段名。從日志文件只能解出對應obj即id,需要通過數據字典查到名稱。其中數據字典中的字段id從1開始,日志文件中從0開始,故需對日志文件中的字段id+1,其他id不需要。


以5.2、5.1開始,5.4結束,中間11.2表示insert操作。圖片中的ERR是DEBUG輸出,請無視~~

解析結果存為SQL語句。