Oracle redo解析之-1、oracle redo log結構計算
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. 複製程式碼