rowid 从字面解释为行标识。在oracle中,通过rowid定位记录是最快和最有效的方式。那么rowid在oracle中是怎样定位记录的哪?并且它为什么是最有效的方式?带着这些问题,让我们一步一步揭开rowid的神秘面纱。
首先,我们看一下怎样获取表中记录的rowid:通过rowid伪列
SQL> select rowid,id from ppp;
ROWID ID
------------------ ----------
AAAS5VAAEAAAAemAAC 3
AAAS5VAAEAAAAemAAD 9
AAAS5VAAEAAAAemAAE 7
我们知道,oracle在逻辑上有多个表空间构成,每个表空间又包含多个数据文件,数据文件对应操作系统上的文件(asm情况类似,不做考虑),而数据文件又包含若干数据块,我们的表记录就是存储在数据块中。因此,如果我们知道某条记录所存储的数据文件、数据块和在块中的偏移量,就可以非常快速的读取记录并展现给用户。rowid恰恰可以帮助我们实现这一点,因为在rowid字符串中包含了数据文件、数据块和记录在块中地址的信息。
在8i之前,oracle采用受限的rowid(Restricted rowid),受限rowid是针对整个数据库范围的rowid,由三部分构成,即数据文件编号,块编号和记录在块内的偏移量。受限rowid占用6个字节的存储空间,其中数据文件编号占用10bit,数据块编号占用22bit,偏移量即记录在数据块中的行号占用16bit。从这里我们也可看出在8i之前的数据库中:
每个数据库最多包含1022个文件(2个文件预留)
每个文件最多可以有4m个数据块
每个块最多可以存储64k条记录。
受限的rowid在底层存储使用二进制格式,展现时采用varchar2和16进制混合的形式:BBBBBBBB.RRRR.FFFF (block#.row#.file#),如:
SQL> select dbms_rowid.rowid_to_restricted('AAAS5VAAEAAAAemAAE',0) from dual;
DBMS_ROWID.ROWID_T
------------------
000007A6.0004.0004
为了达到以上种种目的,oracle引入了相对文件号的概念,这种方法的主要思想是改变之前rowid中数据文件编号是参考整个数据库范围i的事实,将其参考的范围改为表空间,即文件编号为4的文件不再是数据库中编号为4的数据文件,而是某个表空间中编号为4的数据文件。这样我们便可以在不改变物理存储格式的情况下(仅仅是我们在解析rowid内容时的处理逻辑发生了变化,如将前10bit解析为表空间相对文件号rfn,而不是文件号file_id,然后通过数据字典视图将),进行数据库的扩容等等。
SQL> select file_id,relative_fno from dba_data_files;
FILE_ID RELATIVE_FNO
---------- ------------
4 4
3 3
2 2
1 1
5 5
6 6
7 6
8 8
9 9
从这里我们看到file_id和relative_fno是一一相等的,其实不然,在数据文件数量没有超过1022个时,oracle数据库尽量保持file_id和relative_fno的相同,在超过1022个数据文件后,oracle就会保证在整个数据库内file_id是唯一的,在单个表空间中relative_fno是唯一的。
那么这时就会存在一个问题,不同表空间中的具有相同相对文件号数据文件oracle是怎样区分开来的那?为了解决这个问题,oracle在原有6byte rowid的基础上又添加了DATA_OBJECT_ID的信息,构成扩展rowid,即扩展rowid由四部分构成:data_object_id,rfn,block#,row#。通过data_object_id 和数据字典视图的结合,oracle可以非常快速的将rfn转换为file_id,从而也就可以准确的进行行定位。
我们以11g中索引的存储格式为例,总结如下:
普通表:
--普通索引:6字节
row#0[8020] flag: ------, lock: 0, len=12
col 0; len 2; (2): c1 02
col 1; len 6; (6): 01 00 05 e3 00 00
row#1[8008] flag: ------, lock: 0, len=12
col 0; len 2; (2): c1 03
col 1; len 6; (6): 01 00 05 e3 00 01
row#2[7996] flag: ------, lock: 0, len=12
col 0; len 2; (2): c1 04
col 1; len 6; (6): 01 00 05 e3 00 02
--全局分区:
row#0[8020] flag: ------, lock: 0, len=12
col 0; len 2; (2): c1 02
col 1; len 6; (6): 01 00 05 e3 00 00
row#1[8008] flag: ------, lock: 0, len=12
col 0; len 2; (2): c1 03
col 1; len 6; (6): 01 00 05 e3 00 01
row#2[7996] flag: ------, lock: 0, len=12
col 0; len 2; (2): c1 04
col 1; len 6; (6): 01 00 05 e3 00 02
----- end of leaf block dump -----
分区表:
--全局分区:分区 10字节
col 0; len 2; (2): c1 02
col 1; len 10; (10): 00 01 2e 55 01 00 07 a6 00 00
row#1[7984] flag: ------, lock: 0, len=16
col 0; len 2; (2): c1 04
col 1; len 10; (10): 00 01 2e 55 01 00 07 a6 00 02
row#2[7968] flag: ------, lock: 0, len=16
col 0; len 2; (2): c1 08
col 1; len 10; (10): 00 01 2e 55 01 00 07 a6 00 04
row#3[7952] flag: ------, lock: 0, len=16
col 0; len 2; (2): c1 0a
col 1; len 10; (10): 00 01 2e 55 01 00 07 a6 00 03
---本地索引:6字节
row#0[8020] flag: ------, lock: 0, len=12
col 0; len 2; (2): c1 04
col 1; len 6; (6): 01 00 07 a6 00 02
row#1[8008] flag: ------, lock: 0, len=12
col 0; len 2; (2): c1 08
col 1; len 6; (6): 01 00 07 a6 00 04
row#2[7996] flag: ------, lock: 0, len=12
col 0; len 2; (2): c1 0a
col 1; len 6; (6): 01 00 07 a6 00 03
----- end of leaf block dump -----
从上面的讨论可知,如果在数据库中使用了tts,那么对于某一rowid在数据库范围内可能不是唯一的,但是在表级别肯定是唯一的,如下所示:
SQL> create tablespace ts5 datafile '/home/easy/oracle/oradata/easy/ts501.dbf' size 10m;
表空间已创建。
SQL> create user zy identified by zy default tablespace ts5;
用户已创建。
SQL> grant connect,resource to zy;
授权成功。
SQL> conn zy/zy
已连接。
SQL> create table test1(name varchar2(10));
表已创建。
SQL> insert into test1 values('thgdk');
已创建 1 行。
SQL> insert into test1 values('zhuuuyy');
已创建 1 行。
SQL> commit;
提交完成。
SQL> conn / as sysdba
已连接。
SQL> alter tablespace ts5 read only;
表空间已更改。
SQL> ho cp /home/easy/oracle/oradata/easy/ts501.dbf /home/easy/oracle/oradata/easy/ts601.dbf
SQL> host expdp system/oracle dumpfile=ts5.dmp directory=tsdir transport_tablespaces=ts5
Export: Release 11.2.0.3.0 - Production on 星期六 11月 16 10:49:03 2013
Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved.
连接到: Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
启动 "SYSTEM"."SYS_EXPORT_TRANSPORTABLE_01": system/******** dumpfile=ts5.dmp directory=tsdir transport_tablespaces=ts5
处理对象类型 TRANSPORTABLE_EXPORT/PLUGTS_BLK
处理对象类型 TRANSPORTABLE_EXPORT/TABLE
处理对象类型 TRANSPORTABLE_EXPORT/POST_INSTANCE/PLUGTS_BLK
已成功加载/卸载了主表 "SYSTEM"."SYS_EXPORT_TRANSPORTABLE_01"
******************************************************************************
SYSTEM.SYS_EXPORT_TRANSPORTABLE_01 的转储文件集为:
/home/easy/oracle/oradata/easy/ts5.dmp
******************************************************************************
可传输表空间 TS5 所需的数据文件:
/home/easy/oracle/oradata/easy/ts501.dbf
作业 "SYSTEM"."SYS_EXPORT_TRANSPORTABLE_01" 已于 10:50:15 成功完成
SQL> alter tablespace ts5 rename to old_ts5;
表空间已更改。
SQL> create user zy_new identified by zy;
用户已创建。
SQL> grant connect,resource to zy_new;
授权成功。
SQL> host impdp system/oracle dumpfile=ts5.dmp directory=tsdir transport_datafiles=/home/easy/oracle/oradata/easy/ts601.dbf remap_schema=zy:zy_new
Import: Release 11.2.0.3.0 - Production on 星期六 11月 16 11:14:28 2013
Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved.
连接到: Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
已成功加载/卸载了主表 "SYSTEM"."SYS_IMPORT_TRANSPORTABLE_01"
启动 "SYSTEM"."SYS_IMPORT_TRANSPORTABLE_01": system/******** dumpfile=ts5.dmp directory=tsdir transport_datafiles=/home/easy/oracle/oradata/easy/ts601.dbf remap_schema=zy:zy_new
处理对象类型 TRANSPORTABLE_EXPORT/PLUGTS_BLK
处理对象类型 TRANSPORTABLE_EXPORT/TABLE
处理对象类型 TRANSPORTABLE_EXPORT/POST_INSTANCE/PLUGTS_BLK
作业 "SYSTEM"."SYS_IMPORT_TRANSPORTABLE_01" 已于 11:14:30 成功完成
SQL> conn zy/zy
已连接。
SQL> select rowid,name from test1;
ROWID NAME
------------------ ----------
AAAS5uAAKAAAACEAAA thgdk
AAAS5uAAKAAAACEAAB zhuuuyy
SQL> conn zy_new/zy
已连接。
SQL> select rowid,name from test1;
ROWID NAME
------------------ ----------
AAAS5uAAKAAAACEAAA thgdk
AAAS5uAAKAAAACEAAB zhuuuyy
SQL> conn / as sysdba
已连接。
SQL> select object_id,data_object_id from dba_objects where object_name='TEST1';
OBJECT_ID DATA_OBJECT_ID
---------- --------------
77422 77422
77467 77422
SQL> ALTER TABLESPACE OLD_TS5 READ WRITE;
表空间已更改。
到现在为止,相信大家对受限rowid和扩展rowid已经非常了解了。由于平时工作中大家与扩展rowid接触的机会比较多,下面我们来总结一下扩展rowid的相关知识。
扩展rowid占用10byte的存储空间,32bite表示DATA_OBJECT_ID,10BIT相对文件编号,22bit数据块和16bit行号。当我们通过查询语句获取rowid时,展现给我们的是18个字符构成的字符串,其中:
数据对象编号 文件编号 块编号 行编号
OOOOOO FFF BBBBBB RRR
这些字符串是10byte信息的64位编码表示
A-Z <==> 0 - 25 (26)
a-z <==> 26 - 51 (26)
0-9 <==> 52 - 61 (10)
+/ <==> 62 - 63 (2)
依据数学知识,我们可以将rowid转换为DATA_OBJECT_ID,RFN,BLOCK#,ROW#。那么有没有更方便的方法?oracle为我们提供了dbms_rowid包,下面看具体用法:
SQL> select dbms_rowid.rowid_object(rowid) object_id,
2 dbms_rowid.rowid_relative_fno(rowid) file_id,
3 dbms_rowid.rowid_block_number(rowid) block_id,
4 dbms_rowid.rowid_row_number(rowid) rnum from tab2;
OBJECT_ID FILE_ID BLOCK_ID RNUM
---------- ---------- ---------- ----------
77168 1 91673 0
77168 1 91673 1