DUL工具是Oracle數據庫挽救數據的最後手段,你用到DUL的時候,大部分狀況下,數據庫已經不能啓動了,甚至有些數據文件已經損壞了。那麼DUL又是怎樣在這些極端的狀況下把數據導出來的呢?下面咱們就來一步步的分析它的工做原理。若是你想本身開發一個相似的工具,這篇文章也會告訴你有那些工做要作,該怎樣去作。數據庫
Oracle數據庫其實是一堆數據的集合,數據存儲在表中,經過一些軟件來管理這些數據,其中讀取數據只是這些功能中的一小部分。在這些數據中最重要的就是用戶數據,它們一般保存在數據文件中,按照必定的格式存儲。這些數據怎樣解釋成咱們看到的樣子,這就須要元數據的幫忙了,一般咱們把元數據叫作數據字典。下面咱們先來看看數據字典是什麼樣子。bootstrap
Oracle的數據字典也是由一些表組成的。其中最主要的有obj$,tab$,col$這三張表,obj$表中指定了對象的名稱,對象ID,對象的數據ID等,固然也指定了對象的屬主ID。tab$表指定了表的一些屬性,最主要的是它指定了表開始的位置,在哪一個數據文件中,從哪一個塊開始。col$表指定了表的列屬性,包括列的名稱,列ID,列在段中的ID,列的類型,長度等等,有了col$中的信息,Oracle就能解釋存儲在數據塊中的表的格式了。數據結構
上面咱們說過,tab$表中有兩個字段指定了表開始的位置,一個叫FILE#,指示表在哪一個數據文件中,另外一個叫BLOCK#,指示表從哪一個塊開始。這個開始的塊叫段頭塊,裏面包含了一個個extent地址範圍,叫作extent map,extent是由連續的數據塊構成的。有了這個extent map,就能夠從這些塊中讀取數據了,這些塊就是表的數據塊。若是一個表很是大,段頭塊並不能包含全部的extent,那怎麼辦呢?Oracle會在這個塊中指定下一個extent map的塊地址,直到全部的extent map都列舉完畢。ide
有了上面的知識,咱們就能夠從數據文件中讀取表的數據了。在開始以前,好像還有一點問題,怎樣從數據文件中讀取數據字典表呢?數據字典也是表,表的段頭位置又是從tab$中獲得的,這時咱們尚未讀到tab$的數據,好像陷入死循環了。別急,Oracle在啓動的時候會遇到跟咱們同樣的問題,它怎麼來解決呢?原來Oracle啓動時,先在內存中建立一個叫作bootstrap$的表,這個表中存儲了一些建表語句,其中就包括了上面提到的obj$,tab$和col$,有趣的是,在每一個建表語句的後面,還指示了這個表的段頭塊位置,那麼這下就方便了,直接到這個位置找到extent map,遍歷全部的extent map找到屬於這個表的數據塊,解析數據塊中的內容,就能夠獲得數據字典的信息了。
看到這裏,你還有點困惑,那麼說明你思考的深刻,是的,bootstrap$表的開始位置在哪裏呢?它保存在1號數據文件的1號塊中,這個塊包含文件頭信息,裏面有個叫root dba的字段,包含的地址就是bootstrap$表的段頭塊地址。工具
數據塊中包含了表中的數據,它也是有必定結構的,開始是塊頭信息,事務信息,下面是ITL,ITL大小是固定的,叫作事務槽,塊中包含幾個事務槽,在事務信息中指定。再後面就是數據頭信息,緊接着是表目錄(table directory)信息,後面是行目錄(row directory),行目錄指定了每行數據的位置。再後面就是行數據了,行數據是從塊的底部往上來存儲的,因此在行目錄和真正的數據之間可能會有一部分空閒的空間。數據塊的結構比較複雜,好在Oracle有一個工具叫作bbed,能夠打開一個數據塊,它詳細定義了這些數據結構,包含數據結構的各個字段,能夠方便的看到數據存儲的細節。對象
LONG類型的數據通常比較長,很容易形成行鏈接,固然若是一個表建立時字段過多,也會形成行鏈接,就是說一行數據分佈在了兩個或多個數據塊之間,這時怎麼辦呢?Oracle在每行數據開始都有一個叫作fb的字段,指示數據是否鏈接到了下一個塊,若是到了下一個塊,那麼就會出現一個叫作nrid的字段,用來指示後面的數據鏈接到了哪裏,這是一個地址,表明了在哪個塊的哪一個偏移量。若是下一個塊尚未徹底容納這一行數據,那麼會有下一個nrid,一直鏈接下去,直到數據行結束。索引
LOB是大對象數據類型,是爲了替代LONG類型引入的,當數據量比較小時,它存儲在表的塊內,若是數據比較大,就存儲在表外的一個段中,這個段叫作LOB段。LOB數據在LOB段中的位置,由一個叫作定位器的字段來指定,英文名稱叫作Lob Locator,這個定位器存儲在表的數據塊中,這樣讀到LOB字段時,就能夠經過定位器找到LOB數據。事務
其實,LOB的存儲是至關複雜的,默認的狀況下,爲了方便存儲,LOB列在表的數據塊中,不只存儲了定位器,還存儲了一些LOB數據塊的地址,經過這些地址把LOB數據讀出來。可是這些存儲的地址個數是有限制的,這取決於表數據塊中LOB信息的長度,默認狀況下最可能是12個,若是超出了,就要用到定位器了,定位器不能直接找到LOB段的塊位置,實際上他是LOB index的一個鍵值,經過這個鍵,在LOB索引中找到一系列的LOB塊的地址,經過這些地址把LOB數據讀出來。內存
上面談到的LOB存儲格式叫作BasicFile LOB,從11g開始,Oracle引入了一種新的LOB存儲格式,叫作SecureFile LOB。它幾乎把LOB index取消了,而是把LOB的塊地址直接放在了LOB段的頭塊中,經過頭塊中的地址能夠直接讀取LOB數據。固然若是LOB數據量很大很大,頭塊也放不下這麼多地址,那怎麼辦呢?Oracle在頭塊中設置了四個地址,分別叫作dba0,dba1,dba2,dba3。這是一個四級的內部樹結構,dba0至關於一個葉子節點,管理了不少LOB數據塊地址,當dba0滿了,就會出現dba1,是dba0的上級節點,它又管理了不少相似dba0的葉子,每一個葉子節點塊都包含了不少LOB數據塊的地址,dba1滿了,就會出現dba2節點,類推上去,到了dba3時,能管理的數據量已經遠超過了LOB數據量的最大限制,這樣全部的LOB數據都能經過這個結構遍歷讀取了。開發
若是你刪除了表,從11g開始默認狀況下並無真正刪除,而是把表名改變了,原來的表名存儲在了一個叫回收站的表中,若是你改變了主意,還能夠經過命令恢復回來,對誤刪了表是一個好消息。因爲它與普通表沒有區別,因此經過上面的知識,咱們就能恢復回來。
若是一個表被截斷了,那可能你就真的訪問不了原來的數據了,若是如今後悔了,也只能去撞牆了。用咱們前面介紹的方法能不能找回數據呢?找到表的段頭塊,dump出來看看,你會發現段頭塊中的extent map已經被清除了,這就無法經過extent map把數據遍歷出來了。辦法老是有的,數據不是都存儲在數據文件中嗎?那咱們把全部數據文件中的塊都掃描一遍,把跟這個表的ID一致的那些塊都找出來,而後從這些塊中把數據都分析出來,不就能夠了嗎?只是花費的時間多一些,而且要保證不能遺漏了數據文件,實踐證實仍是能夠把數據讀出來的。
有了上面截斷表的經驗,刪除的表也就好處理了。段頭塊的改變幾乎與截斷表是同樣的。與截斷表不一樣的是,你要先把數據字典中刪除的記錄恢復出來,obj$,tab$,col$表中關於這個表的記錄都被刪除了,那麼怎樣恢復呢?記得前面咱們提到過在每行數據前面都有一個叫作fb的字段,其實Oracle並無把這條數據清除掉,只是在fb字段上作了一個標記,去除這個標記,這些記錄就都恢復了。下面再把數據文件掃描一遍,找到屬於這個表的塊,就能把數據恢復了。
最嚴重的狀況就是數據文件中有部分已經損壞了,那麼就不能保證徹底恢復數據了。那麼首先仍是要嘗試讀取數據字典,Oracle對基礎的數據字典表存儲的段頭塊位置都是固定的,找一個相同版本的數據庫,從bootstrap$表中查找到數據字典的段頭位置,或者從tab$中找到段頭位置,而後嘗試從這些地方導出數據字典,若是能導出數據字典,剩下的工做就跟前面同樣了。最壞的狀況就是系統表空間的數據文件丟失或者嚴重損壞,已經沒法導出數據字典了,那麼這時怎麼辦呢?那麼只有經過數據來重建數據字典了,仍是把全部數據文件掃描一遍,記錄段頭塊的位置,每一個段頭塊會對應一個表或一個分區,這樣表的段頭位置就找到了,接下來要作的就是重建col$中的字段,主要是數據類型,長度等。有些數據類型的長度是固定的,好比日期類型,時間戳類型,很好猜想。數字類型也有本身的特色比較好肯定,剩下的就是字符類型了,大部分猜想不到的均可以先當作字符類型處理。而後根據重建的數據字典導出一部分數據,這時就要經過人工比對,把字段類型肯定清楚,而後就能夠導出數據了。