作者囉嗦
demo程式移步:github.com/zhoubihui/r… ,redo log分析的幾個階段如下(Oracle 11g R2):
- redo結構計算(已更新)
- BBED & DUMP 工具的使用(已更新)
- 常見change的分析
- rowid計算(已更新)
- Oracle常用資料型別的底層儲存與轉換
- redo怎麼區分事務
- IMU模式與非IMU模式下redo record的不同
- undo segment、undo block和data block的鏈子
- 完整事務計算,並根據事務構建sql
- 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傳到備機執行。
前言
要從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區分事務。
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、檔案序號等)。
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的塊頭結構一致。
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 -- 校驗值,塊寫入時計算得到。
操作記錄(redo record)的結構:
1. 一個記錄頭(redo record header)
2. 多個change。
redo record header
record header的長度有24位元組和68位元組兩種情況。
typedef struct record_header0{
unit4_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.
複製程式碼