详解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 -----
相关阅读: