**作者简介:**严军(花名吉远),十年以上专注于数据库存储领域,精通Oracle、Mysql、OceanBase,对大数据、分布式、高并发、高性能、高可用有丰富的经验。主导过蚂蚁集团核心系统数据库升级,数据库LDC单元化多活项目,常年负责蚂蚁重大数据库活动(如双11、双12、春节红包大促),现任阿里云数据库架构师和云计算专家,专注于金融行业数据库架构设计和咨询工作。
因最近笔者在协助金融客户完成系统的平滑迁移与升级工作。随着迁移系统数量的不断增加,发现客户对Oracle的使用的复杂度和深度都相当高。因此,本文列举了Oracle的索引组织表、大对象的特性,以及迁移到OceanBase(简称OB)的相应方案,以供参考。希望能对大家有所帮助。
1、索引组织表(Index-Organized Table, IOT)
1.1 索引组织表介绍
索引组织表是一类特殊的表,它将索引和表的数据存储在一起(或者说实际上将所有数据都放入了索引中)。普通表的数据以无序(Heap)的方式存放在数据库中。而索引组织表按照主键进行排序,以二叉树的形式对表的数据进行存储。
●索引组织表不存储ROWID,它通过主键来访问数据。
●索引组织表适合通过主键对数据进行访问的应用。
优点
1.快速的随机访问。索引和表的数据存储在一起,如果对表进行更新,Oracle只更新索引结构。
2.快速的范围扫描。索引组织表已经按照主键对数据进行排序,因此,范围扫描的速度是很快的。
3.更少的存储需求。索引数据和表的数据存储在一起,可以减少存储需求。普通的索引条目只包含索引值和指向数据行的ROWID
组成
1.索引部分。存放主键值,频繁访问的部分非主键值,指向溢出区的ROWID
2.溢出部分。用于存放非主键值。溢出区存放在一个溢出表空间中。用户可以指定溢出表空间。
1.2 问题业务场景
客户反馈OMS迁移时部分SYS_IOT开头的表迁移出现报错,原来以为是系统表,但核对时发现通过 user_objects查询能查到这个表。
最后确定这类表其实都是索引组织表的子表,且子表不支持任何操作。复现脚本如下:
-- 创建IOT时,必须要设定主键,否则报错。
CREATE TABLE TEST_IOT
(id NUMBER PRIMARY KEY,
C1 VARCHAR2(50),
C2 VARCHAR2(10))
ORGANIZATION INDEX PCTTHRESHOLD 10 OVERFLOW;
select object_name,object_type from user_objects where object_name like '%IOT%';
/*
SYS_IOT_OVER_96983 TABLE
SYS_IOT_TOP_96983 INDEX
TEST_IOT TABLE
*/
-- 只需要对父表进行赋权和获取DDL操作,子表不支持任何操作。
grant select on SYS_IOT_OVER_96983 to scott;
-- ORA-25191: cannot reference overflow table of an index-organized table
select dbms_metadata.get_ddl('TABLE','SYS_IOT_OVER_96983','APPTEST') from dual;
/* ORA-31603: object "SYS_IOT_OVER_96983" of type TABLE not found in schema "APPTEST"
ORA-06512: at "SYS.DBMS_METADATA", line 6478
ORA-06512: at "SYS.DBMS_SYS_ERROR", line 105
ORA-06512: at "SYS.DBMS_METADATA", line 6465
ORA-06512: at "SYS.DBMS_METADATA", line 9202
ORA-06512: at line 1
*/
-- 查询父子表对应关系
select TABLE_NAME,IOT_NAME from dba_tables where owner='APPTEST';
TEST_IOT
SYS_IOT_OVER_96983 TEST_IOT
1.3 迁移方案
●表结构迁移:通过dbcat进行结构迁移,索引组织表会被转换成普通表(后续OMS会支持);
●数据迁移:由于Logminer捕获不到增量日志,这类表只能离线迁移。离线迁移方案建议:
○主键单字段场景:OMS效率尚可,也可以使用dataX或者导出CSV文件后导入OB的方式;
○主键多字段场景:不建议使用OMS,建议datax或者导出方式。
2. 大对象(LOB类型)
2.1 大对象介绍
Oracle
Oracle包含4种大对象类型BLOB、CLOB、NCLOB、BFILE,存储长度都为4G。具体如下:
●CLOB:内部字符大对象,存储单字节和多字节字符数据。支持固定宽度和可变宽度的字符集,常用于大文本的存储。
●NCLOB:国家语言字符集大对象,存储UNICODE类型的数据,支持固定宽度和可变宽度的字符集。
●BLOB:内部二进制大对象,存储非结构化的二进制数据大对象,它可以被认为是没有字符集语义的比特流,一般是图像、声音、视频等文件。
●BFILE:外部二进制文件,存储在数据库外的系统文件,只读的,数据库会将该文件当二进制文件处理。
OB
LOB 全称为大对象数据类型(Large Object),包括 BLOB 和 CLOB,用来存储大型和非结构化数据,例如文本、图像、视频和空间数据等。本文主要提供 OceanBase 数据库当前版本所支持的大对象数据类型的概览和使用限制。
大对象数据类型概览
OceanBase 数据库当前版本所支持的大对象数据类型的信息如下表所示。
|------|----|------------|-----------|
| 类型 | 长度 | 定义长度上限(字符) | 字符集 |
| BLOB | 变长 | 48 MB | BINARY |
| CLOB | 变长 | 48 MB | 与租户的字符集一致 |
说明:与 Oracle 通过 LOB Locator 引用数据不同,在 OceanBase 数据库中,LOB Locator 与数据保存在同一结构中。
2.2 迁移方案
●BLOB/CLOB:确定在OB支持的最大长度范围内,平迁。超出最大范围需要业务改造;
●NCLOB:OB不支持 nclob,OMS在迁移中会将字段类型转成 nvarchar2 。注意nvarchar2 上限为 32767 个字节;
●BFILE:建议在Oracle中使用代码转成BLOB后迁移,案例如下:
-- BFile实际上是Oracle数据库指向操作系统文件的一个指针。
-- 读取操作系统文件并转为BLOB代码:
-- 创建测试文件
touch /home/oracle/test.jpg
-- 创建directory对象
create directory obtest as '/home/oracle/xxx';
grant read on directory obtest to <username>;
create table obfiletest(col1 bfile,cole2 blob);
INSERT INTO obfiletest(col1) VALUES (BFILENAME ('obtest', 'test.jpg'));
-- BFILE转BLOB
Declare
v_bfile Bfile;
v_blob Blob;
v_dest Number := 1;
v_lang Number := 1;
Begin
v_bfile := bfilename('OBTEST', 'test.jpg');
--dbms_output.put_line(v_bfile);
dbms_lob.createtemporary(v_blob, True);
dbms_lob.fileopen(v_bfile, dbms_lob.file_readonly);
dbms_output.put_line(dbms_lob.getlength(v_bfile));
dbms_lob.loadblobfromfile(dest_lob => v_blob, src_bfile => v_bfile,
amount => dbms_lob.getlength(v_bfile),
dest_offset => v_dest, src_offset => v_lang);
Update obfiletest Set cole2 = v_blob;
dbms_lob.fileclose(file_loc => v_bfile);
End;
/
2.3 关于CLOB字段的UNIQUE INDEX
●在创建CLOB字段的同时,Oracle会自动创建一个UNIQUE INDEX;OB支持CLOB字段类型,但不会自动创建UNIQUE INDEX(手工创建会报错)
●方案:OB不支持该index,直迁数据即可(OMS迁移会忽略)。在做对象校验时需要忽略这类index。
create table test001 (
uinfo clob
);
insert into test001 values ('
{
"employees":[
{"firstName":"Bill","lastName":"Gates","creation_time":"2021-01-01","age":"30"},
{"firstName":"George","lastName":"Bush","creation_time":"2021-06-01","age":"26"},
{"firstName":"Thomas","lastName":"Carter","creation_time":"2020-03-01","age":"23"}
]
}
');
commit;
select * from dba_indexes where table_name='TEST001';
SELECT INDEX_NAME,TABLE_NAME,TABLE_OWNER,DBMS_METADATA.get_ddl('INDEX',INDEX_NAME,'APPTEST') INDEX_DDL
FROM user_indexes --当前用户下的索引
WHERE table_name = 'TEST001';
-- 对应DDL
CREATE UNIQUE INDEX "APPTEST"."SYS_IL0000101865C00001$$" ON "APPTEST"."TEST001" (
PCTFREE 10 INITRANS 2 MAXTRANS 255
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE "USERS"
PARALLEL (DEGREE 0 INSTANCES 0)