Oracle資料庫特種恢復技術(一)—原理篇

jinqibingl發表於2012-10-04

Oracle資料庫特種恢復技術(一)—原理篇  

          Oracle資料庫特種恢復技術(一)—原理篇   

                              作者:謝浩

oracle資料庫特種恢復技術(二)—塊內篇連結:http://www.itpub.net/thread-1507766-1-1.html
oracle資料庫特種恢復技術(三)—轉換篇連結:http://www.itpub.net/thread-1507774-1-1.html
Oracle資料庫特種恢復技術(四)—實驗篇連結:http://www.itpub.net/thread-1510202-1-1.html

oracle資料庫特種恢復技術(五)--redo篇連結:http://www.itpub.net/thread-1517926-1-1.html


      由一次ora-00600 [4000]想到的:

      專案測試庫出現過一次由於異常斷電引起的ora-00600 [4000]造成資料庫無法啟動的故障,診斷為系統回滾段資料字典(字典而不是回滾段本身)損壞,且該庫處於非歸檔模式,無任何備份。後使用bbed修改system表空間資料檔案,將相關資料塊的事物狀態手工修改為一致性狀態,資料庫得以開啟。

由此引發另一種思路:如果資料庫無法使用rman、冷備份、dg、ogg、dsg等方式正常恢復開啟,那麼還能否找回其中資料?考慮到業務資料還存在於儲存裝置上(只是完整性有一些缺失),能否在不啟動資料庫的情況下,由應用從儲存裝置上直接讀出資料庫中的業務資料?為解決以上問題,使用dba一些日常工具結合oracle資料庫內部結構的知識,來分析oracle資料庫定位資料的原理。這樣做的目的是最終能夠透過編寫程式碼(java或者c++)實現半自動或全自動的資料庫無法mount狀態下的恢復。


     定位資料的方式:

    Oracle資料庫服務程式在定位某行資料時,採用了資料字典+段頭extent map的“集中—分散”儲存模式,具體為:

              1、在資料字典中存放資料段(對應表或分割槽)頭的物理位置

              2、在資料段頭存放段中所有extent(即連續的block集合)的物理位置

      首先從資料字典入手,oracle資料庫在建立時會生成一系列的基礎資料字典表,這些基礎字典記錄了一個資料庫的物理架構,oracle正是透過這些基礎字典,將邏輯層的表與物理層的儲存進行轉換和對映。其實建庫時執行的catalog.sql等指令碼只是在這些基礎資料字典上建立了相關的檢視、同義詞等(還有一些記憶體表的對映檢視),因此catalog.sql指令碼所建立的“字典”存在與否都不會影響資料庫的正常執行,但基礎資料字典一旦出現異常,資料庫就無法啟動。

oracle記錄段頭物理位置的字典檢視是sys.dba_segments,其中記錄了段頭所在的檔案號及在該檔案中的塊號,其中檔案號對應檔案物理位置在控制檔案中查詢。對於sys.dba_segments檢視,最值得關心或者說與本文關係最為密切的是:

select a.owner,a.segment_name,a.header_file,a.header_block fromdba_segments a;這四個欄位,透過這四個欄位就可以定位某使用者的某個段的段頭在哪個檔案的哪個資料塊內。透過plsql developer等工具檢視sys.dba_segments檢視的定義(sqlplus也可,但作為一個成熟的dba,熟練掌握各種工具的特點,完成不同任務,最大幅度減少達成目標所需的工作量,是一項非常重要的素質),可見其資料來源於sys.sys_dba_segs檢視:

create or replace view sys.dba_segments as

select owner, segment_name, partition_name,segment_type, tablespace_name,

      header_file, header_block,

      decode(bitand(segment_flags, 131072), 131072, blocks,

          (decode(bitand(segment_flags,1),1,

           dbms_space_admin.segment_number_blocks(tablespace_id, relative_fno,

           header_block, segment_type_id, buffer_pool_id, segment_flags,

           segment_objd, blocks), blocks)))*blocksize,

      decode(bitand(segment_flags, 131072), 131072, blocks,

          (decode(bitand(segment_flags,1),1,

           dbms_space_admin.segment_number_blocks(tablespace_id, relative_fno,

           header_block, segment_type_id, buffer_pool_id, segment_flags,

           segment_objd, blocks), blocks))),

      decode(bitand(segment_flags, 131072), 131072, extents,

          (decode(bitand(segment_flags,1),1,

          dbms_space_admin.segment_number_extents(tablespace_id, relative_fno,

          header_block, segment_type_id, buffer_pool_id, segment_flags,

          segment_objd, extents) , extents))),

      initial_extent, next_extent, min_extents, max_extents, pct_increase,

      freelists, freelist_groups, relative_fno,

      decode(buffer_pool_id, 0, 'DEFAULT', 1, 'KEEP', 2, 'RECYCLE', NULL)

    fromsys_dba_segs;

   

    繼續觀察sys.sys_dba_segs檢視的定義,

create or replace view sys.sys_dba_segs

(owner, segment_name, partition_name,segment_type, segment_type_id, tablespace_id, tablespace_name, blocksize,header_file, header_block, bytes, blocks, extents, initial_extent, next_extent,min_extents, max_extents, pct_increase, freelists, freelist_groups, relative_fno,buffer_pool_id, segment_flags, segment_objd)

as

select NVL(u.name, 'SYS'), o.name, o.subname,

      so.object_type, s.type#,

      ts.ts#, ts.name, ts.blocksize,

      f.file#, s.block#,

      s.blocks * ts.blocksize, s.blocks, s.extents,

       s.iniexts * ts.blocksize,

      decode(bitand(ts.flags, 3), 1, to_number(NULL),

                                      s.extsize* ts.blocksize),

      s.minexts, s.maxexts,

      decode(bitand(ts.flags, 3), 1, to_number(NULL),

                                      s.extpct),

      decode(bitand(ts.flags, 32), 32, to_number(NULL),

             decode(s.lists, 0, 1, s.lists)),

      decode(bitand(ts.flags, 32), 32, to_number(NULL),

             decode(s.groups, 0, 1, s.groups)),

      s.file#, s.cachehint, NVL(s.spare1,0), o.dataobj#

from sys.user$ u, sys.obj$ o, sys.ts$ ts,sys.sys_objects so, sys.seg$ s,

    sys.file$ f

where s.file# = so.header_file

  ands.block# = so.header_block

  ands.ts# = so.ts_number

  ands.ts# = ts.ts#

  ando.obj# = so.object_id

  ando.owner# = u.user# (+)

  ands.type# = so.segment_type_id

  ando.type# = so.object_type_id

  ands.ts# = f.ts#

  ands.file# = f.relfile#

union all

select NVL(u.name, 'SYS'), un.name, NULL,

      decode(s.type#, 1, 'ROLLBACK', 10, 'TYPE2 UNDO'), s.type#,

      ts.ts#, ts.name, ts.blocksize, f.file#, s.block#,

      s.blocks * ts.blocksize, s.blocks, s.extents,

      s.iniexts * ts.blocksize, s.extsize * ts.blocksize, s.minexts,

      s.maxexts, s.extpct,

      decode(bitand(ts.flags, 32), 32, to_number(NULL),

        decode(s.lists, 0, 1, s.lists)),

      decode(bitand(ts.flags, 32), 32, to_number(NULL),

        decode(s.groups, 0, 1, s.groups)),

      s.file#, s.cachehint, NVL(s.spare1,0), un.us#

from sys.user$ u, sys.ts$ ts, sys.undo$ un,sys.seg$ s, sys.file$ f

where s.file# = un.file#

  ands.block# = un.block#

  ands.ts# = un.ts#

  ands.ts# = ts.ts#

  ands.user# = u.user# (+)

  ands.type# in (1, 10)

  andun.status$ != 1

  andun.ts# = f.ts#

  andun.file# = f.relfile#

union all

select NVL(u.name, 'SYS'), to_char(f.file#) ||'.' || to_char(s.block#), NULL,

      decode(s.type#, 2, 'DEFERRED ROLLBACK', 3, 'TEMPORARY',

                      4, 'CACHE', 9, 'SPACEHEADER', 'UNDEFINED'), s.type#,

      ts.ts#, ts.name, ts.blocksize,

      f.file#, s.block#,

      s.blocks * ts.blocksize, s.blocks, s.extents,

      s.iniexts * ts.blocksize,

      decode(bitand(ts.flags, 3), 1, to_number(NULL),

                                      s.extsize* ts.blocksize),

      s.minexts, s.maxexts,

      decode(bitand(ts.flags, 3), 1, to_number(NULL),

                                     s.extpct),

      decode(bitand(ts.flags, 32), 32, to_number(NULL),

        decode(s.lists, 0, 1, s.lists)),

      decode(bitand(ts.flags, 32), 32, to_number(NULL),

        decode(s.groups, 0, 1, s.groups)),

      s.file#, s.cachehint, NVL(s.spare1,0), s.hwmincr

from sys.user$ u, sys.ts$ ts, sys.seg$ s,sys.file$ f

where s.ts# = ts.ts#

  ands.user# = u.user# (+)

  ands.type# not in (1, 5, 6, 8, 10)

  ands.ts# = f.ts#

      and s.file# = f.relfile#;

    可見其是由三個sql語句塊聯合而成,而其中2、3語句塊明顯是回滾段等segment,因此著重觀察第一語句塊,

select NVL(u.name, 'SYS'), o.name, o.subname,

      so.object_type, s.type#,

      ts.ts#, ts.name, ts.blocksize,

      f.file#, s.block#,

      s.blocks * ts.blocksize, s.blocks, s.extents,

       s.iniexts * ts.blocksize,

      decode(bitand(ts.flags, 3), 1, to_number(NULL),

                                      s.extsize* ts.blocksize),

      s.minexts, s.maxexts,

      decode(bitand(ts.flags, 3), 1, to_number(NULL),

                                      s.extpct),

      decode(bitand(ts.flags, 32), 32, to_number(NULL),

             decode(s.lists, 0, 1, s.lists)),

      decode(bitand(ts.flags, 32), 32, to_number(NULL),

             decode(s.groups, 0, 1, s.groups)),

      s.file#, s.cachehint, NVL(s.spare1,0), o.dataobj#

from sys.user$ u, sys.obj$ o, sys.ts$ ts,sys.sys_objects so, sys.seg$ s,

    sys.file$ f

where s.file# = so.header_file

  ands.block# = so.header_block

  ands.ts# = so.ts_number

  ands.ts# = ts.ts#

  ando.obj# = so.object_id

  ando.owner# = u.user# (+)

  ands.type# = so.segment_type_id

  ando.type# = so.object_type_id

  ands.ts# = f.ts#

      and s.file# = f.relfile#

可見其資料來源為sys.user$ u, sys.obj$ o, sys.ts$ ts, sys.sys_objects so, sys.seg$s,sys.file$ f這幾個基礎系統字典表(這些已經都是物理存在的表而不是檢視了),而其中最重要的owner、segment_name、header_file、header_block欄位,則分別來自sys.user$、sys.obj$、sys.seg$、sys.file$等字典表,也就是說要在不啟動資料庫的情況下定位到使用者資料,必須先定位以上幾個基本資料字典。那麼這幾個表的物理儲存位置又在哪裡呢?或者說在資料庫不啟動的情況下,如何獲得這四個表的物理儲存位置?要回答這個問題,先要了解一下oracle啟動的過程,oracle在啟動時會載入這些基礎資料字典表,而這些字典表的create語句都寫在sys.bootstrap$表裡,

SQL> select *from sys.bootstrap$ a where a.sql_text like '%SEG$%';


     LINE#      OBJ# SQL_TEXT

---------- ------------------------------------------------------------------------------------------

        14         14 CREATE TABLE SEG$("FILE#"NUMBER NOT NULL,"BLOCK#" NUMBER NOT NULL,"TYPE#" NUMBE

      而sys.bootstrap$表的物理位置其實是間接“寫死”在oracle程式碼中的。

      在每一個oracle資料庫(以10g為例,其他版本相差不大)的system表空間的第一個檔案的header block中都按固定結構存放有kcvfh這個結構體,其定義可以透過bbed工具檢視。

這裡簡單介紹一下bbed工具。Bbed是oracle公司提供的一個文字編輯工具(有些dba把bbed想象的非常神秘高深,其實它就是一個普通的文字編輯器,用其他文字編輯工具(比如UltraEdit)的宏功能其實可以實現bbed的絕大部分功能),透過它可以方便的檢視oracle資料在物理層面的儲存結構。

      為安全考慮,先把資料庫system表空間的第一個檔案複製到另外的目錄以便觀察。在sqlplus中執行select file#||' '||name||' '||bytes from v$datafile ;將結果中的路徑和檔名修改為先前資料檔案的複製,用來生成bbed列表檔案,這樣用bbed開啟的就是這份複製檔案,而不是真正的庫中的檔案。Bbed安裝編譯過程不再贅述。

    進入bbed後預設偏移量是1,即file header block,

    BBED> map

     File:/home/oracle/vie.pdf (1)

     Block: 1                                     Dba:0x00400001

    ------------------------------------------------------------

     DataFile Header

   

     struct kcvfh, 676 bytes                    @0      

   

     ub4tailchk                               @8188  

    Map命令會自動檢測當前資料塊的type,並顯示該型別資料塊內所包含的資料結構資訊(這也就是為什麼設定不同偏移量,map輸出的資料結構資訊也會不同),這裡看到,system表空間的file header block中包含一個名為kcvfh的結構體,該結構體佔用676 bytes,深入觀察:

    BBED> p kcvfh

    struct kcvfh, 676 bytes                     @0      

      struct kcvfhbfh, 20 bytes               @0      

          ub1type_kcbh                         @0        0x0b

         ub1 frmt_kcbh                        @1        0xa2

         ub1 spare1_kcbh                      @2        0x00

         ub1 spare2_kcbh                      @3        0x00

         ub4 rdba_kcbh                         @4        0x00400001

         ub4 bas_kcbh                         @8        0x00000000

         ub2 wrp_kcbh                         @12       0x0000

         ub1 seq_kcbh                         @14       0x01

         ub1 flg_kcbh                          @15       0x04 (KCBHFCKV)

         ub2 chkval_kcbh                      @16       0x2671

         ub2 spare3_kcbh                      @18       0x0000

      struct kcvfhhdr, 76 bytes               @20      

         ub4 kccfhswv                          @20       0x00000000

         ub4 kccfhcvn                         @24       0x0a200100

         ub4 kccfhdbi                         @28       0xea382478

         text kccfhdbn[0]                     @32      P

         text kccfhdbn[1]                      @33      H

         text kccfhdbn[2]                     @34      O

         text kccfhdbn[3]                     @35      N

         text kccfhdbn[4]                     @36      E

         text kccfhdbn[5]                     @37      D

         text kccfhdbn[6]                     @38      B

         text kccfhdbn[7]                     @39      

         ub4 kccfhcsq                         @40       0x00001ae3

         ub4 kccfhfsz                         @44       0x00017c00

         s_blkz kccfhbsz                      @48       0x00

         ub2 kccfhfno                         @52       0x0001

         ub2 kccfhtyp                         @54       0x0003

         ub4 kccfhacid                        @56       0x00000000

         ub4 kccfhcks                          @60       0x00000000

         text kccfhtag[0]                     @64      

         text kccfhtag[1]                     @65      

         text kccfhtag[2]                     @66      

         text kccfhtag[3]                     @67      

         text kccfhtag[4]                     @68      

         text kccfhtag[5]                     @69      

         text kccfhtag[6]                     @70      

         text kccfhtag[7]                     @71      

         text kccfhtag[8]                      @72      

         text kccfhtag[9]                     @73      

         text kccfhtag[10]                    @74      

         text kccfhtag[11]                    @75      

         text kccfhtag[12]                    @76      

         text kccfhtag[13]                    @77      

         text kccfhtag[14]                    @78      

         text kccfhtag[15]                    @79      

         text kccfhtag[16]                    @80      

         text kccfhtag[17]                     @81      

         text kccfhtag[18]                    @82      

         text kccfhtag[19]                    @83      

         text kccfhtag[20]                    @84      

         text kccfhtag[21]                    @85      

         text kccfhtag[22]                    @86      

         text kccfhtag[23]                    @87      

         text kccfhtag[24]                    @88      

         text kccfhtag[25]                    @89      

         text kccfhtag[26]                     @90      

         text kccfhtag[27]                    @91      

         text kccfhtag[28]                    @92      

         text kccfhtag[29]                    @93      

         text kccfhtag[30]                    @94      

         text kccfhtag[31]                    @95      

       ub4kcvfhrdb                            @96       0x00400179

      struct kcvfhcrs, 8 bytes                @100     

         ub4 kscnbas                          @100      0x00000008

    。。。。。。。。。。。。。。。。。

    省略後面內容

    這裡的 ub4 kcvfhrdb  @96 0x00400179就是oracle啟動過程中非常關鍵的變數:root dba,root dba中記錄了sys.bootstrap$的物理位置,因此可以把root dba理解為指向sys.bootstrap$的指標。這裡該變數的值為0x00400179,是按照dba(datablock address)的格式存放,將其轉換為二進位制後的前10位為檔案號,後22位為塊號 0x00400179轉換為二進位制0000 0000 0100 0000 0000 0001 0111 1001,檔案號部分:0000 0000 01,塊號部分:00 0000 0000 0001 01111001,表示1號檔案的377號塊,這個數字不是我的庫的巧合,而是10g版本資料庫的固定位置,在oracle 10g資料庫啟動時會預設在該偏移量的位置尋找指向sys.bootstrap$表的指標。在11g中sys.bootstrap$的位置改到了0x00400208。而10g、11g該指標的位置(即rootdba變數自己的位置)是相同的,都是file header block的96偏移量位置。需要注意的是這裡的偏移量96,並不是從檔案頭部開始計算:oracle在建立datafile時,實際生成的檔案會比管理員輸入的檔案尺寸大一個db_block_size的大小,比如某資料庫的db_block_size=8k,建立表空間時指定某檔案大小為100M,那麼該檔案的實際大小是100M+8K,這8K放在檔案的最前面,稱作os header block,root dba(也就是指向sys.bootstrap$的指標)的物理偏移量是db_block_size+96。這也就是為什麼在一些使用裸裝置的庫上,在把裸裝置交給oracle的時候,不能把檔案尺寸寫的和裝置的尺寸完全一樣,而要稍小一點,否則建立時會出現ORA-27042錯誤。

    這裡有一個小技巧,在使用bbed分析資料檔案結構時,要觀察在不同主機上的不同版本的庫,有些庫的資料檔案很大,複製、檢視不便,可以針對需要,使用dd命令擷取重點關注的部分,比如我在觀察11g的file header block時使用 ddif=/u01/app/oracle/oradata/mydb/system01.dbf f=/home/oracle/lt140_header2.tracecount=1000 skip=0 bs=8192擷取該檔案的前8M資料,生成一個檔案,然後傳輸到主要用來做分析工作的主機上,修改bbed配置檔案指向該檔案就能用bbed對其進行分析,這麼做還有一個原因就是11g是不帶bbed工具的,需要從10g複製原始檔再進行編譯連結。結果導向、儘量簡化中間操作過程是dba工作的重要指導原則。

透過root dba的瞭解,已經可以在不開啟資料庫的情況下定位到sys.bootstrap$表的物理位置,下一個問題是,sys.bootstrap$表裡儲存的只是基礎字典表的建立語句,那麼這些表的物理儲存位置是如何確定的?進一步觀察sys.bootstrap$表的sql_text欄位,以file$為例:SQL> set line 2000

SQL> set headoff

SQL> select *from sys.bootstrap$ a where a.obj# = 17;


        17         17

CREATE TABLEFILE$("FILE#" NUMBER NOT NULL,"STATUS$" NUMBER NOT NULL,"BLOCKS"NUMBER NOT NULL,"TS#" NUMBER,"RELFILE#"NUMBER,"MAXEXTEND" NUMBER,"INC" NUMBER,"CRSCNWRP"NUMBER,"CRSCNBAS" NUMBER,"OWNERINSTANCE"VARCHAR2(30),"SPARE1" NUMBER,"SPARE2"NUMBER,"SPARE3" VARCHAR2(1000),"SPARE4" DATE) PCTFREE 10PCTUSED 40 INITRANS 1 MAXTRANS 255 STORAGE ( INITIAL 64K NEXT 1024K MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0OBJNO 17 EXTENTS (FILE 1 BLOCK 113))

這個create語句的storage子句中有兩個不常見的引數OBJNO 17 和EXTENTS (FILE 1 BLOCK 113),前者是指定該segment的物件號,而extents是直接指定segment的segmentheader block儲存位置,我多次嘗試在客戶發起的sql語句中使用這兩個引數未能成功,判斷為只有在資料庫處於特定狀態或以特定身份執行時才能使用這些引數。就是說這兩個引數直接指定了段在儲存上得物理位置。從該引數已經可以直接定位其所對應的資料字典段頭的物理位置,而oracle的段頭中的extents map結構體就包涵了該段全部的資料塊的實體地址。

    在sys.bootstrap$表中記錄的資料字典表的create語句,還有一部分是沒有直接指定儲存位置的,這些表都是某個簇的成員,其實際資料儲存在簇中,比如SYS.TAB$:

    CREATE TABLE TAB$("OBJ#" NUMBERNOT NULL,"DATAOBJ#" NUMBER,"TS#" NUMBER NOTNULL,"FILE#"

。。。。。。。。。。。。。。。。。。。。省略。。。。。。。。。。。。。。。。。。。。。。。。。。。。

NUMBER,"SPARE2"NUMBER,"SPARE3" NUMBER,"SPARE4"VARCHAR2(1000),"SPARE5" VARCHAR2(1000),"SPARE6" DATE)STORAGE (  OBJNO 4 TABNO 1) CLUSTERC_OBJ#(OBJ#)

    紅色字型白色背景的就是指定SYS.TAB$表加入到C_OBJ#簇中。


至此,在關閉資料庫的情況下定位資料塊的大致思路已經清晰,總結如下:

1、在system 表空間的第一個資料檔案的特定偏移位置,找到root dba變數

2、root dba變數的值就是指向sys.bootstrap$表的物理位置的指標

3、sys.bootstrap$表中記錄了資料庫基礎字典表的物理位置

4、基礎字典表內記錄了使用者段段頭的物理儲存位置

下一步就是要透過某個段的segmentheader block從資料檔案中直接讀出表的資料。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/9606200/viewspace-745677/,如需轉載,請註明出處,否則將追究法律責任。

相關文章