在oracle数据库中rowid是唯一的吗,或许很多人第一反应都是这么认为。这种说法其实并不准确,应该说在oracle 8以前,在整个数据库层面rowid是唯一的,而从oracle 8开始,在数据库层面rowid并不是全局唯一的,针对表空间来说,存储在其中的数据的rowid则是相对唯一的。

我们知道在数据库中存放的每一条记录都有一个ROWID ,代表着这条记录在数据库存放的物理地址。

从oracle 8开始ROWID的格式发生了变化,大小也从8字节增加到了10个字节。当你对某个表进行重组或者导出导入,则ROWID会变化。比如对于一个分区表来说,如果通过update语句将数据行从一个分区迁移到另一个分区,则其rowid会发生变化。

Oracle 7格式

在Oracle 7中ROWID由8个字节组成:

  • 字节1到4 (bits 1 to 32): 数据块编号 (0-4294967295)
  • 字节5到6 (bits 33 to 48): 数据块里的行编号 (0-65535)
  • 字节7到8 (bits 49 to 64): 数据文件编号(0-65535)

每个字节由2个16进制的字符表示 (0-9A-F),同时每个部分由圆点隔开: BBBBBBBB.RRRR.FFFF

这个时候数据库里文件数最大为1022,当时使用已经足够,而随着业务的不断发展,所需要的文件数也越来越大,1022也明显不够用。到oracle 8时,想增加数据文件数量的同时又不想修改已有数据的rowid,因为这会牵涉到在升级过程中需要修改所有数据块的巨大工作量,所以对rowid的组成部分进行了变更。

Oracle 8格式

在Oracle 8中ROWID由10个字节组成:

  • 字节1到4 (bits 1 to 32): data object id (0-4294967295)
  • 字节5和半个字节6 (bits 33 to 44):表空间里的文件编号(0-4095)
  • 半个字节6和字节7和8 (bits 45 to 64):文件里的块编号(0-1048575)
  • 字节9和字节10 (bits 65 to 80):数据块里的行编号(0-65535)

每个字段均以基数64显示 (A-Za-z0-9+/): OOOOOOFFFBBBBBBRRR,对于大文件表空间来说,文件和块的字段组合一起来给出大文件中块的编号。

Relative file number

从oracle 8起,为了不影响已有rowid,做了很多工作:

  1. 引入了‘relative file number’,文件标识部分对于数据库并不是唯一的,只对表空间表示唯一。这表示每个表空间会有1022个数据文件,而不是整个数据库。
  2. 对于数据库中的前1022个文件来说,relative_fno的值与file_id相同,所以这样oracle 7中的数据文件是可以兼容的:之前file_id的数值现在就变成了relative_fno。
  3. 当数据库的数据文件超过1022时,file_id可以继续增加(现在它不储存在rowid里了),但是relative_fno则重置为1。

这样产生的结果就是rowid再也不是唯一的了,当我们不得不通过rowid去查询数据行时,我们必须得知道它处在哪个表空间下面,因为rowid的组成部分与表空间相关联。

在很早之前的版本中,因为每个表只会处于一个表空间下面。例如当表处在USERS表空间下,当通过唯一索引去查询行的rowid,那我们会知道rowid是处于USERS表空间,因为跟表一样。

然而当出现了分区表,这种情况就不再使用,每个分区都可以处在不同的表空间下面。这就是为什么要引入扩展rowid的原因,例如分区表的全局索引,比原来的rowid更大,除了给出relative file number的信息外还指出了表空间信息。它被称为扩展rowid,与原先的rowid区分开,后者仅适用于我们知道表空间的情况。

Data object id

在旧版本的oracle中只有一个object_id,因为逻辑表(table)和物理表(segment)之间是一对一的关系,这种情况在分区表出来以后发生了改变,因为一个逻辑表会有多个物理表(partition)组成。

每一个分区都是一个逻辑表,都有一个唯一的object_id,但是现在引入了一个新的data_object_id用来区分物理对象,在表的创建过程中,data_object_id是等于object_id的,然而这个值会发生变化。

当truncate表时,只需要重置表的高水位线,而不用去处理所有的数据块。然后你插入新的数据会覆盖原来的位置。但是为了避免其他正在操作的用户混淆原数据块和新的,truncate操作会改变data_object_id用来表示这是一个新的段,这个段仍与原来的逻辑表相关联。

通过分区技术可以进行交换分区:逻辑object_id发生了改变,但是物理data_object_id未发生变化,因为数据存放的仍然是同一个段内。

通常在逻辑层面上我们用到的都是object_id,比如锁表和锁分区,称作OBJECT_ID, OBJ# 或者 OBJN。而在处理物理段时,都会用到data_object_id,比如坏块、cache中的块时,会看到DATA_OBJECT_ID, DATAOBJ# 或 OBJD等。

ROWID不唯一

根据上面的介绍知道对于ROWID来说表空间是可以独立于数据库的,只需要在表空间中唯一即可。这也在通过TTS导入表空间的时候,就不需要对大量涉及到的相关索引、链表等的ROWID进行重新关联、修改对应关系等,对于导入的数据也只需要让它保持原样存放即可,这样会很大程度上提高TTS的导入效率。

SQL> create tablespace ts1 datafile size 20m;

Tablespace created.

SQL> create table t1 tablespace ts1 as select '1' id from dual;

Table created.

SQL> select * from t1;

ID
---
1

SQL> select rowid from t1;

ROWID
------------------
AAAk0XACOAAAACDAAA

SQL> alter tablespace ts1 read only;

Tablespace altered.

[oracle@testyum datafile]$ cp o1_mf_ts1_hrz542bl_.dbf ts2.dbf

# 导出表空间
[oracle@testyum u01]$ expdp system/oracle@pdb12c dumpfile=ts1.dmp directory=expdp_dir transport_tablespaces=ts1

Connected to: Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
Starting "SYSTEM"."SYS_EXPORT_TRANSPORTABLE_01":  system/********@pdb12c dumpfile=ts1.dmp directory=expdp_dir transport_tablespaces=ts1 
Processing object type TRANSPORTABLE_EXPORT/TABLE_STATISTICS
Processing object type TRANSPORTABLE_EXPORT/STATISTICS/MARKER
Processing object type TRANSPORTABLE_EXPORT/PLUGTS_BLK
Processing object type TRANSPORTABLE_EXPORT/POST_INSTANCE/PLUGTS_BLK
Processing object type TRANSPORTABLE_EXPORT/TABLE
Processing object type TRANSPORTABLE_EXPORT/POST_TABLE_ACTION
Master table "SYSTEM"."SYS_EXPORT_TRANSPORTABLE_01" successfully loaded/unloaded
******************************************************************************
Dump file set for SYSTEM.SYS_EXPORT_TRANSPORTABLE_01 is:
  /u01/app/oracle/ts1.dmp
******************************************************************************
Datafiles required for transportable tablespace TS1:
  /u01/app/oracle/oradata/ORA12C/86DF3648F8F79A5AE0536892A8C00070/datafile/o1_mf_ts1_hrz542bl_.dbf
  
# 重新导入表空间
SQL> alter tablespace ts1 rename to ts2;

Tablespace altered.

SQL> grant dba to test identified by test;

Grant succeeded.

[oracle@testyum u01]$ impdp system/oracle@pdb12c dumpfile=ts1.dmp directory=expdp_dir transport_datafiles=/u01/app/oracle/oradata/ORA12C/86DF3648F8F79A5AE0536892A8C00070/datafile/ts2.dbf remap_schema=xb:test

Connected to: Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
Master table "SYSTEM"."SYS_IMPORT_TRANSPORTABLE_01" successfully loaded/unloaded
Starting "SYSTEM"."SYS_IMPORT_TRANSPORTABLE_01":  system/********@pdb12c dumpfile=ts1.dmp directory=expdp_dir transport_datafiles=/u01/app/oracle/oradata/ORA12C/86DF3648F8F79A5AE0536892A8C00070/datafile/ts2.dbf remap_schema=xb:test 
Processing object type TRANSPORTABLE_EXPORT/PLUGTS_BLK
Processing object type TRANSPORTABLE_EXPORT/TABLE
Processing object type TRANSPORTABLE_EXPORT/TABLE_STATISTICS
Processing object type TRANSPORTABLE_EXPORT/STATISTICS/MARKER
Processing object type TRANSPORTABLE_EXPORT/POST_TABLE_ACTION
Processing object type TRANSPORTABLE_EXPORT/POST_INSTANCE/PLUGTS_BLK

# rowid是一模一样
SQL> select rowid from xb.t1;

ROWID
------------------
AAAk0XACOAAAACDAAA

SQL> select rowid from test.t1;

ROWID
------------------
AAAk0XACOAAAACDAAA

通过上面这个测试可以验证rowid确实可以重复,也间接可以说明在这样的数据库环境中多个segment可以含有相同的data_object_id

select owner,object_id,data_object_id
from dba_objects
where object_name='T1';

OWNER                           OBJECT_ID DATA_OBJECT_ID
------------------------------ ---------- --------------
XB                                 150807         150807
TEST                               150858         150807

因为新的rowid包含Relative file number,所以也可以得到他们的Relative file number是相同的。

select tablespace_name, file_id, relative_fno from dba_data_files
  where tablespace_name in('TS1','TS2');
  
TABLESPACE_NAME         FILE_ID RELATIVE_FNO
-------------------- ---------- ------------
TS2                         142          142
TS1                         143          142

而当对表进行ALTER TABLE MOVE操作时,为move的表创建了一个新段,这个新段的data_object_id同样会不一样,从而ROWID也会不一样。

SQL> alter tablespace ts2 read write;

Tablespace altered.

SQL> select rowid from t1;

ROWID
------------------
AAAk0XACOAAAACDAAA

SQL> alter table t1 move;

Table altered.

SQL> select rowid from t1;

ROWID
------------------
AAAk1NACOAAAACLAAA		---不一致

select owner,object_id,data_object_id
from dba_objects
where object_name='T1';

OWNER                           OBJECT_ID DATA_OBJECT_ID
------------------------------ ---------- --------------
XB                                 150807         150861	-- data_object_id发生改变
TEST                               150858         150807