Oracle DUL的工作原理和技術(shù)實(shí)現(xiàn)
DUL工具是Oracle數(shù)據(jù)庫(kù)挽救數(shù)據(jù)的最后手段,你用到DUL的時(shí)候,大部分情況下,數(shù)據(jù)庫(kù)已經(jīng)不能啟動(dòng)了,甚至有些數(shù)據(jù)文件已經(jīng)損壞了。那么DUL又是怎樣在這些極端的情況下把數(shù)據(jù)導(dǎo)出來(lái)的呢?下面我們就來(lái)一步步的分析它的工作原理。如果你想自己開(kāi)發(fā)一個(gè)類似的工具,這篇文章也會(huì)告訴你有那些工作要做,該怎樣去做。
Oracle數(shù)據(jù)庫(kù)實(shí)際上是一堆數(shù)據(jù)的集合,數(shù)據(jù)存儲(chǔ)在表中,通過(guò)一些軟件來(lái)管理這些數(shù)據(jù),其中讀取數(shù)據(jù)只是這些功能中的一小部分。在這些數(shù)據(jù)中最重要的就是用戶數(shù)據(jù),它們通常保存在數(shù)據(jù)文件中,按照一定的格式存儲(chǔ)。這些數(shù)據(jù)怎樣解釋成我們看到的樣子,這就需要元數(shù)據(jù)的幫忙了,通常我們把元數(shù)據(jù)叫做數(shù)據(jù)字典。下面我們先來(lái)看看數(shù)據(jù)字典是什么樣子。
數(shù)據(jù)字典
Oracle的數(shù)據(jù)字典也是由一些表組成的。其中最主要的有obj$,tab$,col$這三張表,obj$表中指定了對(duì)象的名稱,對(duì)象ID,對(duì)象的數(shù)據(jù)ID等,當(dāng)然也指定了對(duì)象的屬主ID。tab$表指定了表的一些屬性,最主要的是它指定了表開(kāi)始的位置,在哪個(gè)數(shù)據(jù)文件中,從哪個(gè)塊開(kāi)始。col$表指定了表的列屬性,包括列的名稱,列ID,列在段中的ID,列的類型,長(zhǎng)度等等,有了col$中的信息,Oracle就能解釋存儲(chǔ)在數(shù)據(jù)塊中的表的格式了。
表在數(shù)據(jù)文件中的位置
上面我們說(shuō)過(guò),tab$表中有兩個(gè)字段指定了表開(kāi)始的位置,一個(gè)叫FILE#,指示表在哪個(gè)數(shù)據(jù)文件中,另一個(gè)叫BLOCK#,指示表從哪個(gè)塊開(kāi)始。這個(gè)開(kāi)始的塊叫段頭塊,里面包含了一個(gè)個(gè)extent地址范圍,叫做extent map,extent是由連續(xù)的數(shù)據(jù)塊構(gòu)成的。有了這個(gè)extent map,就可以從這些塊中讀取數(shù)據(jù)了,這些塊就是表的數(shù)據(jù)塊。如果一個(gè)表非常大,段頭塊并不能包含所有的extent,那怎么辦呢?Oracle會(huì)在這個(gè)塊中指定下一個(gè)extent map的塊地址,直到所有的extent map都列舉完畢。
有了上面的知識(shí),我們就可以從數(shù)據(jù)文件中讀取表的數(shù)據(jù)了。在開(kāi)始之前,好像還有一點(diǎn)問(wèn)題,怎樣從數(shù)據(jù)文件中讀取數(shù)據(jù)字典表呢?數(shù)據(jù)字典也是表,表的段頭位置又是從tab$中得到的,這時(shí)我們還沒(méi)有讀到tab$的數(shù)據(jù),好像陷入死循環(huán)了。別急,Oracle在啟動(dòng)的時(shí)候會(huì)遇到跟我們一樣的問(wèn)題,它怎么來(lái)解決呢?原來(lái)Oracle啟動(dòng)時(shí),先在內(nèi)存中創(chuàng)建一個(gè)叫做bootstrap$的表,這個(gè)表中存儲(chǔ)了一些建表語(yǔ)句,其中就包括了上面提到的obj$,tab$和col$,有趣的是,在每個(gè)建表語(yǔ)句的后面,還指示了這個(gè)表的段頭塊位置,那么這下就方便了,直接到這個(gè)位置找到extent map,遍歷所有的extent map找到屬于這個(gè)表的數(shù)據(jù)塊,解析數(shù)據(jù)塊中的內(nèi)容,就可以得到數(shù)據(jù)字典的信息了。
看到這里,你還有點(diǎn)困惑,那么說(shuō)明你思考的深入,是的,bootstrap$表的開(kāi)始位置在哪里呢?它保存在1號(hào)數(shù)據(jù)文件的1號(hào)塊中,這個(gè)塊包含文件頭信息,里面有個(gè)叫root dba的字段,包含的地址就是bootstrap$表的段頭塊地址。
數(shù)據(jù)塊
數(shù)據(jù)塊中包含了表中的數(shù)據(jù),它也是有一定結(jié)構(gòu)的,開(kāi)始是塊頭信息,事務(wù)信息,下面是ITL,ITL大小是固定的,叫做事務(wù)槽,塊中包含幾個(gè)事務(wù)槽,在事務(wù)信息中指定。再后面就是數(shù)據(jù)頭信息,緊接著是表目錄(table directory)信息,后面是行目錄(row directory),行目錄指定了每行數(shù)據(jù)的位置。再后面就是行數(shù)據(jù)了,行數(shù)據(jù)是從塊的底部往上來(lái)存儲(chǔ)的,所以在行目錄和真正的數(shù)據(jù)之間可能會(huì)有一部分空閑的空間。數(shù)據(jù)塊的結(jié)構(gòu)比較復(fù)雜,好在Oracle有一個(gè)工具叫做bbed,可以打開(kāi)一個(gè)數(shù)據(jù)塊,它詳細(xì)定義了這些數(shù)據(jù)結(jié)構(gòu),包含數(shù)據(jù)結(jié)構(gòu)的各個(gè)字段,可以方便的看到數(shù)據(jù)存儲(chǔ)的細(xì)節(jié)。
LONG數(shù)據(jù)類型
LONG類型的數(shù)據(jù)一般比較長(zhǎng),很容易造成行連接,當(dāng)然如果一個(gè)表創(chuàng)建時(shí)字段過(guò)多,也會(huì)造成行連接,就是說(shuō)一行數(shù)據(jù)分布在了兩個(gè)或多個(gè)數(shù)據(jù)塊之間,這時(shí)怎么辦呢?Oracle在每行數(shù)據(jù)開(kāi)始都有一個(gè)叫做fb的字段,指示數(shù)據(jù)是否連接到了下一個(gè)塊,如果到了下一個(gè)塊,那么就會(huì)出現(xiàn)一個(gè)叫做nrid的字段,用來(lái)指示后面的數(shù)據(jù)連接到了哪里,這是一個(gè)地址,代表了在哪一個(gè)塊的哪個(gè)偏移量。如果下一個(gè)塊還沒(méi)有完全容納這一行數(shù)據(jù),那么會(huì)有下一個(gè)nrid,一直連接下去,直到數(shù)據(jù)行結(jié)束。
LOB數(shù)據(jù)類型
LOB是大對(duì)象數(shù)據(jù)類型,是為了替代LONG類型引入的,當(dāng)數(shù)據(jù)量比較小時(shí),它存儲(chǔ)在表的塊內(nèi),如果數(shù)據(jù)比較大,就存儲(chǔ)在表外的一個(gè)段中,這個(gè)段叫做LOB段。LOB數(shù)據(jù)在LOB段中的位置,由一個(gè)叫做定位器的字段來(lái)指定,英文名稱叫做Lob Locator,這個(gè)定位器存儲(chǔ)在表的數(shù)據(jù)塊中,這樣讀到LOB字段時(shí),就可以通過(guò)定位器找到LOB數(shù)據(jù)。
Lob Index
其實(shí),LOB的存儲(chǔ)是相當(dāng)復(fù)雜的,默認(rèn)的情況下,為了方便存儲(chǔ),LOB列在表的數(shù)據(jù)塊中,不僅存儲(chǔ)了定位器,還存儲(chǔ)了一些LOB數(shù)據(jù)塊的地址,通過(guò)這些地址把LOB數(shù)據(jù)讀出來(lái)。但是這些存儲(chǔ)的地址個(gè)數(shù)是有限制的,這取決于表數(shù)據(jù)塊中LOB信息的長(zhǎng)度,默認(rèn)情況下最多是12個(gè),如果超出了,就要用到定位器了,定位器不能直接找到LOB段的塊位置,實(shí)際上他是LOB index的一個(gè)鍵值,通過(guò)這個(gè)鍵,在LOB索引中找到一系列的LOB塊的地址,通過(guò)這些地址把LOB數(shù)據(jù)讀出來(lái)。
SecureFile
上面談到的LOB存儲(chǔ)格式叫做BasicFile LOB,從11g開(kāi)始,Oracle引入了一種新的LOB存儲(chǔ)格式,叫做SecureFile LOB。它幾乎把LOB index取消了,而是把LOB的塊地址直接放在了LOB段的頭塊中,通過(guò)頭塊中的地址可以直接讀取LOB數(shù)據(jù)。當(dāng)然如果LOB數(shù)據(jù)量很大很大,頭塊也放不下這么多地址,那怎么辦呢?Oracle在頭塊中設(shè)置了四個(gè)地址,分別叫做dba0,dba1,dba2,dba3。這是一個(gè)四級(jí)的內(nèi)部樹(shù)結(jié)構(gòu),dba0相當(dāng)于一個(gè)葉子節(jié)點(diǎn),管理了很多LOB數(shù)據(jù)塊地址,當(dāng)dba0滿了,就會(huì)出現(xiàn)dba1,是dba0的上級(jí)節(jié)點(diǎn),它又管理了很多類似dba0的葉子,每個(gè)葉子節(jié)點(diǎn)塊都包含了很多LOB數(shù)據(jù)塊的地址,dba1滿了,就會(huì)出現(xiàn)dba2節(jié)點(diǎn),類推上去,到了dba3時(shí),能管理的數(shù)據(jù)量已經(jīng)遠(yuǎn)超過(guò)了LOB數(shù)據(jù)量的最大限制,這樣所有的LOB數(shù)據(jù)都能通過(guò)這個(gè)結(jié)構(gòu)遍歷讀取了。
Recyclebin
如果你刪除了表,從11g開(kāi)始默認(rèn)情況下并沒(méi)有真正刪除,而是把表名改變了,原來(lái)的表名存儲(chǔ)在了一個(gè)叫回收站的表中,如果你改變了主意,還可以通過(guò)命令恢復(fù)回來(lái),對(duì)誤刪了表是一個(gè)好消息。由于它與普通表沒(méi)有區(qū)別,所以通過(guò)上面的知識(shí),我們就能恢復(fù)回來(lái)。
Truncate Table
如果一個(gè)表被截?cái)嗔耍强赡苣憔驼娴脑L問(wèn)不了原來(lái)的數(shù)據(jù)了,如果現(xiàn)在后悔了,也只能去撞墻了。用我們前面介紹的方法能不能找回?cái)?shù)據(jù)呢?找到表的段頭塊,dump出來(lái)看看,你會(huì)發(fā)現(xiàn)段頭塊中的extent map已經(jīng)被清除了,這就沒(méi)法通過(guò)extent map把數(shù)據(jù)遍歷出來(lái)了。辦法總是有的,數(shù)據(jù)不是都存儲(chǔ)在數(shù)據(jù)文件中嗎?那我們把所有數(shù)據(jù)文件中的塊都掃描一遍,把跟這個(gè)表的ID一致的那些塊都找出來(lái),然后從這些塊中把數(shù)據(jù)都分析出來(lái),不就可以了嗎?只是花費(fèi)的時(shí)間多一些,并且要保證不能遺漏了數(shù)據(jù)文件,實(shí)踐證明還是可以把數(shù)據(jù)讀出來(lái)的。
Drop Table
有了上面截?cái)啾淼慕?jīng)驗(yàn),刪除的表也就好處理了。段頭塊的改變幾乎與截?cái)啾硎且粯拥摹Ec截?cái)啾聿煌氖牵阋劝褦?shù)據(jù)字典中刪除的記錄恢復(fù)出來(lái),obj$,tab$,col$表中關(guān)于這個(gè)表的記錄都被刪除了,那么怎樣恢復(fù)呢?記得前面我們提到過(guò)在每行數(shù)據(jù)前面都有一個(gè)叫做fb的字段,其實(shí)Oracle并沒(méi)有把這條數(shù)據(jù)清除掉,只是在fb字段上做了一個(gè)標(biāo)記,去除這個(gè)標(biāo)記,這些記錄就都恢復(fù)了。下面再把數(shù)據(jù)文件掃描一遍,找到屬于這個(gè)表的塊,就能把數(shù)據(jù)恢復(fù)了。
數(shù)據(jù)字典損壞
最嚴(yán)重的情況就是數(shù)據(jù)文件中有部分已經(jīng)損壞了,那么就不能保證完全恢復(fù)數(shù)據(jù)了。那么首先還是要嘗試讀取數(shù)據(jù)字典,Oracle對(duì)基礎(chǔ)的數(shù)據(jù)字典表存儲(chǔ)的段頭塊位置都是固定的,找一個(gè)相同版本的數(shù)據(jù)庫(kù),從bootstrap$表中查找到數(shù)據(jù)字典的段頭位置,或者從tab$中找到段頭位置,然后嘗試從這些地方導(dǎo)出數(shù)據(jù)字典,如果能導(dǎo)出數(shù)據(jù)字典,剩下的工作就跟前面一樣了。
最壞的情況就是系統(tǒng)表空間的數(shù)據(jù)文件丟失或者嚴(yán)重?fù)p壞,已經(jīng)無(wú)法導(dǎo)出數(shù)據(jù)字典了,那么這時(shí)怎么辦呢?那么只有通過(guò)數(shù)據(jù)來(lái)重建數(shù)據(jù)字典了,還是把所有數(shù)據(jù)文件掃描一遍,記錄段頭塊的位置,每個(gè)段頭塊會(huì)對(duì)應(yīng)一個(gè)表或一個(gè)分區(qū),這樣表的段頭位置就找到了,接下來(lái)要做的就是重建col$中的字段,主要是數(shù)據(jù)類型,長(zhǎng)度等。有些數(shù)據(jù)類型的長(zhǎng)度是固定的,比如日期類型,時(shí)間戳類型,很好猜測(cè)。數(shù)字類型也有自己的特點(diǎn)比較好確定,剩下的就是字符類型了,大部分猜測(cè)不到的都可以先當(dāng)做字符類型處理。然后根據(jù)重建的數(shù)據(jù)字典導(dǎo)出一部分?jǐn)?shù)據(jù),這時(shí)就要通過(guò)人工比對(duì),把字段類型確定清楚,然后就可以導(dǎo)出數(shù)據(jù)了。

浙公網(wǎng)安備 33010602011771號(hào)