详解ORACLE ROWID之来龙去脉

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的发展,我们需要突破某些限制,例如单个数据库最多1022个数据文件的限制,同时我们还要保持数据库的向后兼容性以及为数据库的某些特性如表空间迁移做出充分的考虑。在这种情况下,仅仅只是通过扩展受限rowid的存储长度是不够的,例如我们将数据文件编号占用的存储空间从10bit扩增到20bit,虽然可以在单个数据库中存储更多的数据文件,但是却增加了向后兼容的难度(因为物理存储格式发生了变化),而且在使用表空间迁移时,如果从旧版本迁移到8i之后的版本,我们需要扫描整个迁移的表空间来修改其中存储的rowid信息,这显然与表空间迁移的初衷(通过拷贝文件和导入元数据信息来导入表空间)是相违背的。

为了达到以上种种目的,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 -----

多数索引存储的是6byte的rowid信息(解析为rfn),通过索引定位记录时,oracle将6 byte rowid和对应表的DATA_OBJECT_ID构成扩展rowid,然后通过扩展rowid定位记录。在分区表的 全局索引中,直接存储扩展rowid,是因为分区表的各个分区可能存储在不同的表空间中,而各个分区的DATA_OBJECT_ID是不同的,因此rowid中直接存储各个分区段的扩展rowid。另一方面,分区表是在8i后引入的,不存在向后兼容的要求。

从上面的讨论可知,如果在数据库中使用了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

你可能感兴趣的:(详解ORACLE ROWID之来龙去脉)