SYSTEM表空间是Oracle数据库最重要的表空间,在创建数据库时被最先创建,其中包含了数据库的元数据,对于数据库来说生死攸关。
系统表空间是永远不能OFFLINE的,如果SYSTEM表空间OFFLINE,则数据库就无法打开,如果SYSTEM表空间出现故障,则数据库就需要进行介质恢复。在数据库的启动过程中,Oracle也需要通过SYSTEM表空间进行引导。
SYSTEM表空间及root dba
在系统表空间文件头存在一个重要的数据结构root dba,我们可以通过转储数据文件头获得这个信息。在5.2.2中我们曾经提到过,转储文件头可以通过如下命令:
alter session set events 'immediate trace name FILE_HDRS level 10'
从生成的trace文件中,可以获得如下信息(Oracle 9i环境信息摘录):
FILE HEADER: Software vsn=153092096=0x9200000, Compatibility Vsn=134217728=0x8000000 Db ID=1407686520=0x53e79778, Db Name='EYGLE' Activation ID=0=0x0 Control Seq=1299570=0x13d472, File size=27017=0x6989 File Number=1, Blksiz=8192, File Type=3 DATA Tablespace #0 - SYSTEM rel_fn:1 Creation at scn: 0x0000.00000007 04/24/2006 11:34:39 Backup taken at scn: 0x0004.6c2d657e 02/12/2007 15:54:52 thread:1 reset logs count:0x24dc1f7d scn: 0x0004.6c432ec0 recovered at 04/07/2007 21:04:11 status:0x4 root dba:0x004001a1 chkpt cnt: 6939 ctl cnt:6938
root dba仅在SYSTEM表空间的文件头存在,用于定位数据库引导的bootstrap$信息。root dba存储的是用十六进制表示的二进制数,其中包含10位的文件号以及22位的数据块号,将0x004001a1转换为二进制就是0000 0000 0100 0000 0000 0001 1010 0001,前10位为1,代表文件号为1,后22位转换为十进制为417,代表数据文件1上的417号数据块。
当然在数据库中无须如此复杂,我们可以用5.3.2中提到的getbfno来转换上述root dba:
SQL> select getbfno('0x004001a1') bfno from dual; BFNO ------------------------------ datafile# is:1 datablock is:417
那么这个Block上存放的是什么对象呢?
Oracle中独一无二的Cache对象
可以查询一下数据库中file 1 block 417上存放的对象:
SQL> select segment_name,segment_type,header_file,header_block 2 from dba_segments where segment_type='CACHE'; SEGMENT_NA SEGMENT_TYPE HEADER_FILE HEADER_BLOCK ---------- ------------------ ----------- ------------ 1.417 CACHE 1 417
在Oracle 9i中这里存放的是Oracle数据库中独一无二的Cache对象。这个对象的名称来自于文件号和数据块号,1.417正好就是文件1的第417个数据块。
这个Cache对象在Oracle数据库中的含义非同一般,在数据库启动的bootstrap过程中,这个对象之前的所有对象都需要用来bootstrap:
SQL> select b.object_id,a.segment_name,a.segment_type,a.header_block from dba_segments a,dba_objects b 2 where a.segment_name=b.object_name(+) and a.header_file=1 and a.header_block <= 417 3 order by a.header_block 4 / OBJECT_ID SEGMENT_NAME SEGMENT_TYPE HEADER_BLOCK ---------- ------------------------------ ------------------ ------------ SYSTEM ROLLBACK 9 2 C_OBJ# CLUSTER 25 3 I_OBJ# INDEX 49 6 C_TS# CLUSTER 57 7 I_TS# INDEX 65 8 C_FILE#_BLOCK# CLUSTER 73 9 I_FILE#_BLOCK# INDEX 81 10 C_USER# CLUSTER 89 11 I_USER# INDEX 97 15 UNDO$ TABLE 105 17 FILE$ TABLE 113 18 OBJ$ TABLE 121 23 PROXY_DATA$ TABLE 129 24 I_PROXY_DATA$ INDEX 137 25 PROXY_ROLE_DATA$ TABLE 145 26 I_PROXY_ROLE_DATA$_1 INDEX 153 27 I_PROXY_ROLE_DATA$_2 INDEX 161 28 CON$ TABLE 169 29 C_COBJ# CLUSTER 177 30 I_COBJ# INDEX 185 33 I_TAB1 INDEX 193 34 I_UNDO1 INDEX 201 35 I_UNDO2 INDEX 209 36 I_OBJ1 INDEX 217 37 I_OBJ2 INDEX 225 38 I_OBJ3 INDEX 233 39 I_IND1 INDEX 241 40 I_ICOL1 INDEX 249 41 I_FILE1 INDEX 257 42 I_FILE2 INDEX 265 43 I_TS1 INDEX 273 44 I_USER1 INDEX 281 45 I_COL1 INDEX 289 46 I_COL2 INDEX 297 47 I_COL3 INDEX 305 48 I_CON1 INDEX 313 49 I_CON2 INDEX 321 50 I_CDEF1 INDEX 329 51 I_CDEF2 INDEX 337 52 I_CDEF3 INDEX 345 53 I_CDEF4 INDEX 353 54 I_CCOL1 INDEX 361 55 I_CCOL2 INDEX 369 56 BOOTSTRAP$ TABLE 377 1.417 CACHE 417 45 rows selected.
这些对象在CREATE DATABASE过程中通过sql.bsq文件创建,其对象号同样代表了这些对象的创建顺序。再来看看1.417对象中存储的信息,转储数据块可以使用如下命令:
alter system dump datafile 1 block 417
检查生成的跟踪文件,可以获得主要信息如下:
Start dump data blocks tsn: 0 file#: 1 minblk 417 maxblk 417 buffer tsn: 0 rdba: 0x004001a1 (1/417) scn: 0x0004.6c4f907a seq: 0x01 flg: 0x04 tail: 0x907a0d01 frmt: 0x02 chkval: 0xc5e1 type: 0x0d=Compatibility segment Header: size 12 next rdba 0x0 entries 25 offset 536f Compatibility entry for 'COMPATSG': Size: 24 Release 0x134217728 By 0x153092096 Dump of memory from 0x0AA84E34 to 0x0AA84E38 AA84E30 00000000 [....] Compatibility entry for 'BOOTSTRP': Size: 24 Release 0x134217728 By 0x153092096 Dump of memory from 0x0AA84E4C to 0x0AA84E50 AA84E40 00400179 [y.@.]
这个信息记录了BOOTSTRP的信息,00400179正好指向的是file 1 block 377:
SQL> select getbfno('0x00400179') bfno from dual; BFNO ------------------------------ datafile# is:1 datablock is:377
这一数据块上存储的正是数据库的BOOTSTRP$引导表:
SQL> select segment_name from dba_extents 2 where block_id between 377 and blocks + 377 -1; SEGMENT_NAME ------------------------------------------------------------------ BOOTSTRAP$
而1.417对象的前一个对象正是BOOTSTRAP$,这个对象中存放的就是创建这些对象的语句,这些语句在数据库启动时,需要被获取以在内存中创建这些对象,再从硬盘上加载启动数据库必须的元数据,从而启动Oracle数据库:
SQL> select * from bootstrap$ order by obj#; LINE# OBJ# SQL_TEXT ---------- ---------- -------------------------------------------------- -1 -1 8.0.0.0.0 0 0 CREATE ROLLBACK SEGMENT SYSTEM STORAGE ( INITIAL 112K NEXT 1024K MINEXTENTS 1 MAXEXTENTS 32765 OBJN O 0 EXTENTS (FILE 1 BLOCK 9)) 2 2 CREATE CLUSTER C_OBJ#("OBJ#" NUMBER) PCTFREE 5 PCT USED 40 INITRANS 2 MAXTRANS 255 STORAGE ( INITIAL 136K NEXT 1024K MINEXTENTS 1 MAXEXTENTS 214748364 5 PCTINCREASE 0 OBJNO 2 EXTENTS (FILE 1 BLOCK 25)) SIZE 800 3 3 CREATE INDEX I_OBJ# ON CLUSTER C_OBJ# PCTFREE 10 I NITRANS 2 MAXTRANS 255 STORAGE ( INITIAL 64K NEXT 1024K MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCRE ASE 0 OBJNO 3 EXTENTS (FILE 1 BLOCK 49)) ……………………
注意在Oracle 10g/11g中,Cache对象已经不再存在:
SQL> select * from v$version where rownum <2; BANNER ----------------------------------------------------------------------------- Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - 64bi SQL> select count(*) from dba_segments where segment_type='CACHE'; COUNT(*) ---------- 0 SQL> select * from v$version where rownum <2; BANNER -------------------------------------------------------------------------------- Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production SQL> select count(*) from dba_segments where segment_type='CACHE'; COUNT(*) ---------- 0
此时Oracle的root dba里直接存储了指向obj#=56的object,也就是存储指向了表bootstrap$的数据块的地址。
我们现在来模拟一下在10g/11g里,Oracle在启动数据库的时候是如何找到表bootstrap$和bootstrap$里存储的各个基表的创建语句的。
首先,Oracle会去读1号数据文件(通常是system表空间的第一个datafile,如system01.dbf )的文件头,Oracle通过读取文件头里记录的root dba,就知道了指向表bootstrap$的数据块的地址:
BBED> set file 1 block 1 FILE# 1 BLOCK# 1 BBED> p kcvfh.kcvfhrdb ub4 kcvfhrdb @96 0x00400179
从上述结果里我们可以看到,现在root dba指向了0x00400179(即file 1,block 377),接着Oracle会去读root dba指向的数据块,以获得bootstrap$的对象号和其存储的各个基表的创建语句:
BBED> set dba 0x00400179 DBA 0x00400179 (4194681 1,377) BBED> p ktemh struct ktemh, 16 bytes @92 ub4 count_ktemh @92 0x00000001 ub4 next_ktemh @96 0x00000000 ub4 obj_ktemh @100 0x00000038 ub4 flag_ktemh @104 0x40000000
可以看到这里root dba指向的object的对象号实际上是0x00000038,也就是10进制的56,即bootstrap$。
SQL> select to_number('38','XX') from dual; TO_NUMBER('38','XX') -------------------- 56 SQL> select name from sys.obj$ where obj#=56; NAME ------------------------------ BOOTSTRAP$
Oracle已经通过root dba找到了表bootstrap$的对象号(obj#),但现在的问题是:只知道bootstrap$的obj#是不够的,bootstrap$中的记录到底存在哪里?
答案是表bootstrap$中记录的存储地址实际上是存在root dba所指向的数据块的ktetb这个结构里。
好了,我们这里来证明上述观点:
SQL> desc bootstrap$; Name Type Nullable Default Comments -------- -------------- -------- ------- -------- LINE# NUMBER OBJ# NUMBER SQL_TEXT VARCHAR2(4000) SQL> select count(*) from bootstrap$; COUNT(*) ---------- 57 SQL> select * from bootstrap$ where obj#=0; LINE# OBJ# SQL_TEXT --------------- --------- ---------------------------------------------------------------------------------------------- 0 0 CREATE ROLLBACK SEGMENT SYSTEM STORAGE ( INITIAL 112K NEXT 1024K MINEXTENTS 1 MAXEXTENTS 32765 OBJNO 0 EXTENTS (FILE 1 BLOCK 9)) SQL> select dump(0,16) from dual; DUMP(0,16) --------------- Typ=2 Len=1: 80
上述记录里SQL_TEXT为“CREATE ROLLBACK SEGMENT SYSTEM STORAGE ( INITIAL 112K NEXT 1024K MINEXTENTS 1 MAXEXTENTS 32765 OBJNO 0 EXTENTS (FILE 1 BLOCK 9))”,在ultraEdit里看一下其16进制编码,上述语句的16进制编码为:
43 52 45 41 54 45 20 52 4F 4C 4C 42 41 43 4B 20 53 45 47 4D 45 4E 54 20 53 59 53 54 45 4D 20 53 54 4F 52 41 47 45 20 28 20 20 49 4E 49 54 49 41 4C 20 31 31 32 4B 20 4E 45 58 54 20 31 30 32 34 4B 20 4D 49 4E 45 58 54 45 4E 54 53 20 31 20 4D 41 58 45 58 54 45 4E 54 53 20 33 32 37 36 35 20 4F 42 4A 4E 4F 20 30 20 45 58 54 45 4E 54 53 20 28 46 49 4C 45 20 31 20 42 4C 4F 43 4B 20 39 29 29
按照上述观点,在ktetb[0]. ktetbdba所指向的的那个block里一定会有一条记录,这条记录有三个column,这三个column的值分别为80,80和
43 52 45 41 54 45 20 52 4F 4C 4C 42 41 43 4B 20 53 45 47 4D 45 4E 54 20 53 59 53 54 45 4D 20 53 54 4F 52 41 47 45 20 28 20 20 49 4E 49 54 49 41 4C 20 31 31 32 4B 20 4E 45 58 54 20 31 30 32 34 4B 20 4D 49 4E 45 58 54 45 4E 54 53 20 31 20 4D 41 58 45 58 54 45 4E 54 53 20 33 32 37 36 35 20 4F 42 4A 4E 4F 20 30 20 45 58 54 45 4E 54 53 20 28 46 49 4C 45 20 31 20 42 4C 4F 43 4B 20 39 29 29
好了,我们现在来验证一下:
BBED> set dba 0x00400179 DBA 0x00400179 (4194681 1,377) BBED> p ktetb struct ktetb[0], 8 bytes @108 ub4 ktetbdba @108 0x0040017a ub4 ktetbnbk @112 0x00000007 BBED> set dba 0x0040017a DBA 0x0040017a (4194682 1,378) BBED> set offset 8030 OFFSET 8030 BBED> dump File: /iprat02/ipratest/system01.dbf (1) Block: 378 Offsets: 8030 to 8191 Dba:0x0040017a ------------------------------------------------------------------------ 2c010301 80018081 43524541 54452052 4f4c4c42 41434b20 5345474d 454e5420 53595354 454d2053 544f5241 47452028 2020494e 49544941 4c203131 324b204e 45585420 31303234 4b204d49 4e455854 454e5453 2031204d 41584558 54454e54 53203332 37363520 4f424a4e 4f203020 45585445 4e545320 2846494c 45203120 424c4f43 4b203929 292c0103 033e6466 033e6466 09382e30 2e302e30 2e30015b 0601
这里我们可以看到,bootstrap$ 中obj#=0的那条记录确实是存在上述block中,即我们的观点得到证明。
另外,我们从上述证明过程中也可以看出Oracle在10g/11g中会首先访问root dba(这里是0x00400179,即file 1 block 377)以获得表bootstrap$的对象号(obj#)和结构ktetb中所指向的存储bootstrap$中记录的数据块地址,接着就会去读取这个数据块(这里是0x0040017a,即file 1 block 378)以获得bootstrap$存储的各个基表的创建语句。关于这一点,我们也可以从下一节中我们会提到的Oracle在open过程中的10046事件的trace文件中得到佐证:
WAIT #1: nam='db file sequential read' ela= 35 file#=1 block#=377 blocks=1 obj#=-1 tim=17525910818553 WAIT #1: nam='db file scattered read' ela= 7618 file#=1 block#=378 blocks=3 obj#=-1 tim=17525910826329
现在我们来总结一下——Oracle在启动数据库的过程中,会先去读1号数据文件的文件头中记录的root dba,再通过root dba去找bootstrap$中存储的那些数据字典的基表的定义,最后根据这些定义创建数据字典。即所谓的一生二,二生三,三生万物。