不管現階段美國和中國對峙到何種程度,不管桀傲不馴拉里.埃里森如何不看好中國,Oracle仍是數據庫中的一枝獨秀。然而,他山之石可以攻玉,多個國產數據庫在關鍵技術攻關方面的整體水平也已達到國際先進。
國內越來越多的Oracle數據庫開始下線,遷移到開源或者國產數據庫上,o2k支持實時增量的將Oracle數據庫增量變化抽取出來,助力國產化數據庫無縫接管Oracle。
筆者作為數據庫內核的負責人參與實現了o2k來解析Oracle日志,并于2022年2月15號把它開放出來給社區免費使用,期間經歷了各種迷茫,獲得了眾多大佬的指導,最終總算是交出了一份還算不錯的試卷。
作為一個DBA兼開發人員,在整個o2k的實現過程中,對于Oracle數據庫的數據庫理論和工程能力學習了很多,也進一步對數據庫原理有了進一步了解。
借助Oracle的設計理念和實現,我們也看看是否能對新一代的數據庫從業者有一定的幫助和借鑒作用。
國內軟件由于美國的打壓,數據庫作為基礎軟件贏來了一個難得的蓬勃發展期。
曾幾何時,數據庫從業人員只是公司IT團隊->運維團隊里的一個小小部門,而今一個能指導開發正確使用數據庫,選擇數據庫來適配業務來適應業務的數據架構師供不應求。
如果能掌握著數據庫原理,甚至能主動改造數據庫適配相關應用場景的人才年薪至少百萬。
作為計算機皇冠上的其中一粒明珠,數據庫上承業務在線離線之責,下接硬件內核之妙,看到越來越多的人才加入數據庫及數據庫內核隊伍,不勝欣慰!
本系列文章中我們將由淺入深以Oracle日志解析遇到的重重阻塞為例,來介紹在數據庫中常見,而又關鍵的概念,了解數據庫設計思路及工程實現中需要注意的事項。
本文以淺為主,我們先簡單介紹一下數據庫的背景和Oracle日志解析的基礎知識
類似于銀行賬戶系統一樣,張三存入100塊錢會'先'被記錄為'張三增加100'的流水賬,然后再把張三的賬戶從1000塊修改為1100塊。
數據庫為了保證原子性和持久化,也會'先'在redo日志中記錄一筆或者多筆redo record,然后再修改數據庫實際的行記錄
注意,這里的“修改賬戶/修改行記錄”都是在“寫流水賬/寫日志”之后完成的。也就是說redo 先于“數據寫入”,這也就是著名的數據庫write-ahead logging (WAL) 。
Oracle寫數據庫日志采用的是物理日志方式,記錄的是內部數據塊的變化;MySQL在Server層的binlog是邏輯日志,記錄的是邏輯行數據的變化。所以對Oracle的日志解析不僅需要理解日志本身filespace、redo record,change vector的變化,還需要理解Oracle內部數據存儲的格式。
Oracle中有專門的命令,支持將指定的二進制redo日志解析為邏輯的文本文件,類似于MySQL提供的mysqlbinlog工具,方便用戶查看和診斷數據庫問題。
ALTER SYSTEM DUMP LOGFILE '/opt/oracle/oradata/master1/redo01.log';
當然,這個解析出來的文本文件也并不是那么容易理解,下文中我們會對關鍵信息進行解讀。
另外,這個邏輯的文本文件也并不是把所有的二進制redo日志中的信息都解析并輸出出來,例如supplement log這種,就沒有輸出
Oracle默認只記錄變化的信息,類似于MySQL中binlog_row_image=minimal的情況。
也就是說,你執行了update t1 set c2=3 where id=2,它只會記錄c2=3的后鏡像數據和c2=2修改之前的前鏡像數據。
對于主備物理塊同步,這些信息已經足夠了,但是對于數據倉庫或者大數據平臺,不知道到底更新的是哪一行(id=2)的數據,是無法保持跟Oracle的數據一致的。
通過alter database add supplemental log data (primary key) columns;可以讓Oracle在記錄日志的時候順便把primary key主鍵也記錄下來,方便同步工具將變化對應到具體哪一行,這些額外增加的日志稱為supplemental log。
OGG,O2K等同步工具都會要求源端Oracle開啟supplemental log。
WAL日志是邏輯的日志表現形式,一個update的事務更新了5行數據,會產生多個redo record(begin,5行記錄的前鏡像和后鏡像,commit),而這些日志記錄到日志文件中是以一個block一個block(默認為512字節)寫到文件中的
邏輯上,數據庫日志是由一個一個的redo record組成的;
物理上,數據庫日志文件中每個block都有Header和Tail,邏輯的redo record記錄到物理文件中對應關系如下:
說了這么多理論的、務虛的東西,我們來點干貨。
首先,我們看看我們做一個update一行數據到底會生成哪些日志信息,為了排除其他的干擾,我們在做update之前和之后都switch logfile,保證我們查詢的redo log中只有update一個變更
在Oracle上執行的語句如下
## 這里新建一個表用于測試
create table test1 (id number primary key, name varchar2(15) not null, hiredate date default(sysdate) );
insert into test1 (id, name) values (1, 'o2k1');
insert into test1 (id, name, hiredate) values (2, 'o2k2', sysdate);
commit
## 為了排除其他的干擾,我們在做update之前和之后都switch logfile,保證我們查詢的redo log中只有update一個變更
ALTER SYSTEM SWITCH LOGFILE;
update test1 set name='o2k3' where id=2;
commit;
ALTER SYSTEM SWITCH LOGFILE;
## 查詢到底應該從那個歸檔日志中獲取update的變更
select * from v$archived_log;
## 將二進制日志反解析出來
ALTER SYSTEM DUMP LOGFILE '+SSDDG1/chenmm/archivelog/2022_05_12/thread_2_seq_114.1017.1104513037';
## 查看日志被導入到哪個trace文件了
select value from v$diag_info where name='Default Trace File'; # 返回ora_6130.trc
這里我們得到兩個文件:
二進制的redo日志文件"thread_2_seq_114.1017.1104513037"
根據這個二進制日志解析出來的文本文件ora_6130.trc
相關的內容我都放到了文末的附錄中了,大家可以自行查看。
我們先從文本文件ora_6130.trc入手查看一下redo文件的邏輯形式。參考redo邏輯格式(見附錄),可以看到
FILE HEADER:日志文件有日志文件的頭,記錄了Db Id,Db Name的數據庫信息,也記錄了文件大小、文件類型以及Blksiz=512(也就是說,redo物理塊大小為512字節),對比redo物理格式(見附錄)FILE HEADER是放到第一個512字節的BLOCK中。這里的文件號對應的就是日志序號Seq#
FILE HEADER: File Number=3, Blksiz=512, File Type=2 LOG
REDO HEADER:另外這里還記錄了這是RAC的哪個節點,是那個序號的redo日志,scn的范圍,"Thread 0002, Seq# 0000000114, SCN 0x0000004f1aa1-0x0000004f1aa8"。對比redo物理格式REDO HEADER是放到第二個512字節的BLOCK中
descrip:"Thread 0002, Seq# 0000000114, SCN 0x0000004f1aa1-0x0000004f1aa8" thread: 2 nab: 0x4 seq: 0x00000072 hws: 0x2 eot: 0 dis: 0
REDO Record:redo record是redo邏輯日志的最重要的組成部分,數據庫的更新都會記錄到一個一個的Redo Record中,我們執行的update和commit就被記錄成了兩個Redo Record。一個長度為0x0244,一個長度0x00a4
REDO RECORD - Thread:2 RBA: 0x000072.00000002.0010 LEN: 0x0244 VLD: 0x05 ... REDO RECORD - Thread:2 RBA: 0x000072.00000003.0064 LEN: 0x00a4 VLD: 0x01
進一步分解redo record,可以看到redo record又是由一個redo header和多個change vector('CHANGE #?')組成
Redo Header:記錄了Redo的長度LEN: 0x0244,redo record的地址RBA: 0x000072.00000002.0010,SCN:0x0000.004f1aa1等信息
REDO RECORD - Thread:2 RBA: 0x000072.00000002.0010 LEN: 0x0244 VLD: 0x05 SCN: 0x0000.004f1aa1 SUBSCN: 1 05/12/2022 17:10:35 (LWN RBA: 0x000072.00000002.0010 LEN: 0002 NST: 0001 SCN: 0x0000.004f1aa1)
Change Vector:它是數據變化的原子結構,觀察第一個Redo Record,我們可以看到CHANGE #1記錄了事務開始;CHANGE #2記錄了update的undo前鏡像;CHANGE #3記錄了update的redo后鏡像;CHANGE #4記錄了session信息;而第二個Redo Record的CHANGE #1記錄了事務提交的信息
CHANGE #1 TYP:0 CLS:17 AFN:3 DBA:0x00c00080 OBJ:4294967295 SCN:0x0000.004f1121 SEQ:1 OP:5.2 ENC:0 RBL:0 ktudh redo: slt: 0x0013 sqn: 0x00000648 flg: 0x0012 siz: 200 fbi: 0 uba: 0x00c00e4c.0209.23 pxid: 0x0000.000.00000000
在進一步分解change vector,它也是有Change Vector的header和可變數據組成,在Lewis《oracle core》中被稱為“唯一最重要的特性”。header和可變數據的具體信息我們在以后的文章中再詳細介紹。
這里僅介紹幾個最關鍵的信息:
opcode:OP:5.2記錄的是事務開始,OP:5.4記錄的是事務結束,OP:5.1記錄的是前鏡像,OP:11.5記錄的是update的后鏡像,有興趣的同學可以參考lewis記錄的opcode(https://jonathanlewis.wordpress.com/2017/07/25/redo-op-codes/)詳細列表。
xid:事務號,Oracle事務管理起始于undo段,并依此為中心。這也是你看到為什么事務開始5.2、事務結束5.4和undo記錄5.1都是對Layer 5 : Transaction Undo的操作的原因。xid記錄的信息從某種程度上來說記錄的就是在undo上占據了哪個slot。阿里巴巴的GalaxyEngine使用的lizard事務優化跟Oracle的事務異曲同工
CHANGE #1 TYP:0 CLS:17 AFN:3 DBA:0x00c00080 OBJ:4294967295 SCN:0x0000.004f1121 SEQ:1 OP:5.2 ENC:0 RBL:0 CHANGE #2 TYP:0 CLS:18 AFN:3 DBA:0x00c00e4c OBJ:4294967295 SCN:0x0000.004f1120 SEQ:1 OP:5.1 ENC:0 RBL:0 xid: 0x0001.013.00000648 CHANGE #3 TYP:2 CLS:1 AFN:4 DBA:0x010000ad OBJ:98733 SCN:0x0000.004f1a8c SEQ:1 OP:11.5 ENC:0 RBL:0 CHANGE #1 TYP:0 CLS:17 AFN:3 DBA:0x00c00080 OBJ:4294967295 SCN:0x0000.004f1aa1 SEQ:1 OP:5.4 ENC:0 RBL:0
這里要專門說一下undo和redo,由于MVCC多版本的設計:
對所有數據的修改,都需要記錄這個數據修改前的值,即在undo里面記錄前鏡像。對于我們的update就是要記錄name='o2k2'
同時,對所有數據的修改又必須先以Redo的方式記錄到WAL日志中,也出現了在redo中記錄undo信息的情況,即第一個Redo Record的CHANGE #2記錄了update的undo前鏡像。
如果在數據庫做恢復前滾的時候,undo跟表數據一樣也需要恢復
也就是說,張三存了100塊錢,流水賬里面記錄的不是"張三 +100",而是“ 前鏡像:張三 1000;后鏡像:張三 1100”,修改一行數據,可能只是幾個字節的變化,但是數據庫為了保證數據恢復、讀一致性多寫了這么多日志,多做了這么多事情。而如果你深入做數據庫內核,你會發現這個是一個天才的設計。由于文章篇幅問題,這里不細講。
進一步細看的話,你可以看到前鏡像和后鏡像里面具體改的數據,例如,前鏡像數據如下:
ncol: 3 nnew: 1 size: 0 col 1: [ 4] 6f 32 6b 32
這里表示這個表有3列,修改了一個列,大小變化為0,修改的是col1,對應的四個字節為6f 32 6b 32,也就是o2k2 6f 32 6b 32是Oracle的數據存儲格式,跟redo 的存儲結構關系不大。
從上面的邏輯日志可以看出來,Oracle想要對表更新:
首先要在程序中構建一個Redo Record
然后構建幾個Change Vector,包括事務開始、修改數據的前鏡像、修改事務的后鏡像等等
將Redo Record 和Change Vector序列化到Redo Buffer (Oracle有專門的LGWR來刷新Redo Buffer到日志文件中)
最后才是將Change Vector應用到數據塊上去,這里來說,應用的是表還是undo,并沒有太大區別
上面主要介紹的是redo的邏輯結構,是Oracle幫我們解析出來的,一般的疑難問題排查到這一步應該夠了,但是如果你要做日志解析或者想要進一步深入排查,你可能會關注到底他在二進制層面是怎么落地的。
這個章節僅面向1%的讀者,如果你對這塊不感興趣,可以直接跳過這個章節:)
參考附錄redo物理格式,這里是將redo文件拷貝出來以后用Hexdump以十六進制格式輸出的Oracle redo日志
首先看第一個block,是file header的block 第一行,是block header,每個block的開頭16個字節記錄的都是這個block的header。
對比下面的普通block,可以看到0x0022是logfile header, 0x0122是logfile block 第二行開始是redo文件頭file header,這里記錄了幾個比較關鍵的信息:“00 02 00 00”表示0x00 00 02 00 = 512即這個redo log一個block到底有多大(在oracle 11.2以后BLOCKSIZE可以設置為512,1024或者4096), “03 00 00 00”表示0x00 00 00 03 = 3表示一共有3個block。
00000000 00 22 00 00 00 00 c0 ff 00 00 00 00 00 00 00 00 |."....??........| 00000010 65 58 00 00 00 02 00 00 03 00 00 00 7d 7c 7b 7a |eX..........}|{z|
注意:由于我們的Oracle是安裝在little endian小端x86的linux服務器上的,所以“00 02 00 00”表示0x00 00 02 00 = 512需要倒過來一下,如果你的Oracle跑在Big Endian大端的IBM AIX上的時候,“00 02 00 00”表示0x00 02 00 00 = 131072就不用倒過來了
既然知道了一個block的大小為0x00000200,那第一個真正的redo block的起始位置開始的block就是00000000+00000200=00000200,第二個就是00000400,第三個就是00000600
00000200 01 22 00 00 01 00 00 00 72 00 00 00 00 80 25 cd |."......r.....%?| 00000400 01 22 00 00 02 00 00 00 72 00 00 00 10 80 66 66 |."......r.....ff| 00000600 01 22 00 00 03 00 00 00 72 00 00 00 64 80 1b 9a |."......r...d...|
這里“01 00 00 00”, “02 00 00 00” 和“03 00 00 00”就是block序號,表示這是第幾個塊;"72 00 00 00"=0x00000072=114是日志序號,對應的就是redo日志空間中的Seq#號;而"00 80", "10 80", "64 80"對應的是該block中第一個redo record相對block起始地址的偏移量。
00000400起始的redo block的redo record是從“10 80”=0x8010-0x8000=0x10=16即從00000410"44 02 00 00..."開始就是redo record的字節信息了。
00000400 01 22 00 00 02 00 00 00 72 00 00 00 10 80 66 66 |."......r.....ff| 00000410 44 02 00 00 05 6e 00 00 a1 1a 4f 00 01 00 00 23 |D....n..?.O....#|
00000600起始的redo block的redo record是從“64 80”=0x8064-0x8000=0x64=100即從00000664"a4 00 00 00..."開始就是redo record的字節信息了。
00000600 01 22 00 00 03 00 00 00 72 00 00 00 64 80 1b 9a |."......r...d...| 00000610 01 00 03 01 00 00 00 00 00 1a 4f 00 01 00 70 72 |..........O...pr| 00000620 6f 32 6b 33 05 14 00 00 00 00 00 00 00 00 00 00 |o2k3............| 00000630 00 00 00 00 00 00 00 00 00 06 00 00 12 00 04 00 |................| 00000640 00 00 02 00 04 00 04 00 00 00 00 00 03 00 4f f0 |..............O?| 00000650 2a 00 37 00 00 00 00 00 00 04 20 0b ff ff ff ff |*.7....... .????| 00000660 53 59 53 00 a4 00 00 00 01 06 00 00 a2 1a 4f 00 |SYS.?.......?.O.|
我們可以明顯看到一個redo record是可以跨兩個block的。也就是前面我們介紹的邏輯的redo record是可能包含在一個物理redo block中的,也有可能跨多個物理redo block。
第一個redo block比較特殊,"00 80"的起始redo record為0。起始這個redo block中并沒有redo record。而是包含了這個redo file的所屬實例信息,起止SCN等信息,甚至部分信息是以純文本來記錄的“Thread 0002, Seq# 0000000114, SCN 0x0000004f1aa1-0x0000004f1aa8”
綜上所述,本文簡略的介紹了Oracle redo的物理和邏輯格式。
一條update語句實際執行時,在Oracle上經歷的寫undo、記錄WAL,修改數據塊的過程;介紹了Oracle在二進制redo日志中把邏輯的redo record對應記錄到redo block中的。
文末一張圖,簡要總結一下他們的關系:
o2k是沃趣科技自主研發,基于Oracle redo日志的二進制解析工具,解析的結果以canal的protobuf的形式直接寫入到kafka,為數據倉庫、人工智能和大數據不可或缺的數據來源管道。
技術合作和生態合作請聯系:o2k@woqutech.com
服務電話: 400-678-1800 (周??周五 09:00-18:00)
商務合作: 0571-87770835
市場反饋: marketing@woqutech.com
地址: 杭州市濱江區濱安路1190號智匯中?A座1101室