Oracle redo解析之-1、oracle redo log結構計算

門牙發表於2019-04-07

作者囉嗦

  demo程式移步:github.com/zhoubihui/r… ,redo log分析的幾個階段如下(Oracle 11g R2):

  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傳到備機執行。

前言

  要從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區分事務。

Oracle redo解析之-1、oracle redo log結構計算

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位元組
複製程式碼

計算

Oracle redo解析之-1、oracle redo log結構計算
Oracle redo解析之-1、oracle redo log結構計算

[注意]本機是小端機器,高位元組在前,低位元組在後。
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.

複製程式碼

相關文章