Oceanbase BLOB/CLOB LO(Large Object)注意事项
OceanBase v4作为一款分布式关系型数据库,提供了与Oracle数据库相似的LOB(Large Object)类型支持,包括BLOB(Binary Large Object)、CLOB(Character Large Object)以及NCLOB(National Character Large Object)。这些数据类型主要用于存储大量文本、图像、JSON、地理信息等非结构化或半结构化数据。
与Oracle类似,OceanBase中的LOB数据也有两种存储方式:内联存储(In-Row Storage)和外联存储(Out-Of-Row Storage)。内联存储意味着LOB数据与主表数据存储在同一行中,而外联存储则将大型LOB数据存储在单独的段中,仅在主表行中保留指向LOB数据的指针。
在Oracle中,当LOB数据大小超过大约4000字节减去系统开销后,数据会自动转为外联存储。然而,在OceanBase中,这个限制可以通过配置参数LOB_INROW_THRESHOLD
来调整,该参数默认值可能高于Oracle的默认值,例如设置为8192字节。这意味着,只要LOB数据大小不超过8192字节,它就可以与主表数据一起内联存储,从而可能提高查询性能。
Oracle还支持BasicFile和SecureFile类型的LOB存储,其中SecureFile提供压缩和加密功能,以节省存储空间并提高安全性,我在之前的笔记分享过LOB 不当的RETENTION 会导致严重的空间浪费(二)。在从 Oracle 迁移到 OceanBase 时,需要关注LOB对象的存储类型和属性,确保它们在OceanBase中得到适当的处理。我们所说的兼容性不仅包括类型兼容,还包括语法语义和行为兼容。如果 OceanBase 的限制比 Oracle 更宽松,则迁移过程可以正常进行。但如果 OceanBase 的限制更严格,则可能会导致数据无法迁入目标数据库。
一则报错
–env Oceanbase for oracle V3.2
从oracle迁移到OB的应用程序日志错误如下:
SQL=update xxx set lob_col=:xx where oid=:oid The size(18141152) is too large, location: 0 mallocator.cpp(308) dbconnect error oci _error code:100203, msg: the request message The size(18141152) is too large Error-oci _error code:100203, msg: the request message The size(18141152) is too large
查看列定义lob_col BLOB data type, 使用dbms_lob.getlength(lob_col) 确认当前入库的最大也就在 ≈ 18MB 左右。据说应用是C 使用OBCI 驱动.
在上一篇《Oracle MySQL PostgreSQL BLOB & CLOB maximum size Limit》记录过,Oracle 中 CLOB 和 BLOB 类型均可达到 4G 大小,如果这样就OB没办法完全兼容oracle. 在OB 的官方文档
OceanBase 数据库V3 版本所支持的大对象数据类型的信息如下表所示:
类型 | 长度 | 定义长度上限(字符) | 字符集 |
BLOB | 变长 | 48 MB | BINARY |
CLOB | 变长 | 48 MB | 与租户的字符集一致 |
在 OceanBase 早期的版本中<=V3.2.3,LOB 数据存储大小限制在 48MB 以内,但如上面的案例,实际BLOB在写入18M左右就报错了,这对客户使用 LOB 带来了强约束限制, 在V3.2.4中官方显示 其字节的长度上限为 64 MB, 在V4.x 后,通过存储层将 Lob 宏块的数据拆成多条 Lob Meta 进行存储,取数据的时候再将多条 Lob Meta 中的数据聚合成一个连续 Buffer 返回给 SQL 层处理,这样突破了数据存储大小的限制,使得 LOB 存储上限扩展达到了 512MB,后续将持续优化到 TB 级别(暂不确认哪个版本可以实现)。
OB V4 for oracle
类型 | 最大长度 |
---|---|
CHAR |
2000 字节 |
NCHAR |
2000 字节 |
VARCHAR |
32767 字节 |
VARCHAR2 |
32767 字节 |
NVARCHAR2 |
32767 字节 |
BLOB |
536870910 字节 |
CLOB |
536870910 字节 |
在当前主推的OceanBase版本V4.x中,关于LOB(Large Object)类型的数据处理能力,官方文档明确指出其支持的最大尺寸为512MB。如果您正计划从Oracle数据库迁移至OceanBase,并且您的应用程序涉及LOB数据,首要任务是验证您的LOB数据大小是否符合OceanBase的规格。如果您的数据大小落在OceanBase所支持的范围内,那么迁移流程可以顺利进行,无需对数据格式进行调整。
然而,如果您的LOB数据超过了512MB的上限,这就要求您在迁移前对相关业务逻辑进行必要的改造。我们某客户直接删除了超长记录0_0!。在OceanBase早期版本v3中OB不支持 NCLOB,这意味着在从Oracle迁移时,NCLOB数据需要通过NVARCHAR2类型(最大32767字节)进行替代。
此外,OceanBase早期版本也不支持Oracle的BFILE数据类型,这要求在迁移前将BFILE数据转换为BLOB类型。令人欣慰的是,根据OceanBase V4.3的最新官方文档,BFILE数据类型现在已被正式纳入支持列表
统计Oracle CLOB 和 BLOB 字段的最大值:
SELECT MAX(DBMS_LOB.GETLENGTH(COL_CLOB)) AS MAX_CLOB, MAX(DBMS_LOB.GETLENGTH(COL_BLOB)) AS MAX_BLOB FROM tablexxx;
LOB加载方式尝试
在Oracle中lob的加载方式 可以参考我另一篇<Oracle LOB 在12c中的增强>
在Oceanbase中加载方式也基本兼容oracle, 支持使用SQL\PL SQL方式, 近似于 VARCHAR2
和 RAW
类型,这些操作只使用了数据,而忽略了 Locator 部分(Not using the locator); 或使用DBMS_LOB API 的read write等方式更新, 了解更多, 全网没有找到官方加载LOB样例, 因为没有oracle数据库的loadfromfile方法,我们使用PL构造,我们测试一下OB Blob加载(环境OB 企业版 for oracle 3.2.4BP5)
-- try 20M DECLARE c blob; BEGIN dbms_lob.createtemporary(lob_loc => c, cache => FALSE, dur => dbms_lob.call); FOR i IN 1..10240 loop DBMS_LOB.WRITEAPPEND(c,2048,hextoraw(rpad('01',2048,'01'))); END LOOP; INSERT INTO tlob VALUES (2,c); COMMIT; END; / ORA-06502: PL/SQL: number or value error: lob variable length too long at package body oceanbase.dbms_lob.writeappend, line: 142, col: 1 at anonymous block, line:6, col: 0
Note :
另一位朋友反馈在oracle这也没有办法执行,我反复测试发现是参数arg2 ‘amount’ 和arg3 ‘ buffer’大小配置不对. 要 arg3+1 >= arg2*2,在oracle如下
DECLARE c blob; v long raw; BEGIN v:=hextoraw(rpad('1',5999,'1')); dbms_lob.createtemporary(lob_loc => c, cache => FALSE, dur => dbms_lob.call); FOR i IN 1..10240 loop DBMS_LOB.WRITEAPPEND(c,3000,v); END LOOP; INSERT INTO tlob VALUES (2,c); COMMIT; END; / PL/SQL procedure successfully completed. DECLARE c blob; v long raw; BEGIN v:=hextoraw(rpad('1',5998,'1')); dbms_lob.createtemporary(lob_loc => c, cache => FALSE, dur => dbms_lob.call); FOR i IN 1..10240 loop DBMS_LOB.WRITEAPPEND(c,3000,v); END LOOP; INSERT INTO tlob VALUES (2,c); COMMIT; END; / ERROR at line 1: ORA-21560: argument 2 is null, invalid, or out of range ORA-06512: at "SYS.DBMS_LOB", line 1156 ORA-06512: at line 8
继续在OB测试
-- try 10M obclient [SYS]> DECLARE -> c blob; -> BEGIN -> dbms_lob.createtemporary(lob_loc => c, cache => FALSE, dur => dbms_lob.call); -> FOR i IN 1..10240 loop -> DBMS_LOB.WRITEAPPEND(c,1024,hextoraw(rpad('01',2048,'01'))); -> END LOOP; -> INSERT INTO tlob VALUES (2,c); -> COMMIT; -> END; -> / ORA-00600: internal error code, arguments: -4013, No memory or reach tenant memory limit obclient [oceanbase]> select * from __all_virtual_tenant_ctx_memory_info where tenant_id=1001 order by hold desc limit 30; +-----------+---------------+----------+--------+----------------------+--------------+-------------+ | tenant_id | svr_ip | svr_port | ctx_id | ctx_name | hold | used | +-----------+---------------+----------+--------+----------------------+--------------+-------------+ | 1001 | 1**.9*.123.36 | 2882 | 5 | SQL_EXEC_CTX_ID | 103792246784 | 96054620544 | --hold ≈100G memory | 1001 | 1**.9*.123.37 | 2882 | 31 | KVSTORE_CACHE_ID | 1927282688 | 1927282688 | | 1001 | 1**.9*.123.38 | 2882 | 31 | KVSTORE_CACHE_ID | 1862270976 | 1862270976 | | 1001 | 1**.9*.123.36 | 2882 | 0 | DEFAULT_CTX_ID | 1698693120 | 1606241648 | | 1001 | 1**.9*.123.36 | 2882 | 1 | MEMSTORE_CTX_ID | 1075838976 | 1073507456 | | 1001 | 1**.9*.123.38 | 2882 | 1 | MEMSTORE_CTX_ID | 643825664 | 641602176 | | 1001 | 1**.9*.123.37 | 2882 | 1 | MEMSTORE_CTX_ID | 599785472 | 597572736 | | 1001 | 1**.9*.123.38 | 2882 | 5 | SQL_EXEC_CTX_ID | 528482304 | 27776 | | 1001 | 1**.9*.123.37 | 2882 | 5 | SQL_EXEC_CTX_ID | 463470592 | 27776 |
Note:
在全网没有找到OB关于LOB的加载样例,我们的测试发现在加载20M 提示超长报错退出,而在加载10M时又触发了超出租户内存限制,居然占用了近100GB的内存,应该是触发了未知BUG. 在此建议OB在文档方面待丰富,如何正确科学的加载BLOB达到文档给出的上限. 也建议客户无论任何数据库文档上的技术都要实际测试验证。
Note:
怀疑是writeappend的内存使用过大,改用append方法,朋友尝试换了一种方式拼接,终于达到了48M BLOB(再大就会报错,也并不是3.2.4版本 64M的上限),但是注意花费了260多秒。也可能是API的问题,这种速度应用肯定是不能接受的。(不确认测试方法是否科学,后面有新的发现再更新。)
JAVA jdbc Oceanbase的方式测试
package obtest; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class testlob { public static void main(String[] args) { try { //$host:$port/$schema_name String url = "jdbc:oceanbase://192.*.*.101:2883/test?pool=false"; // 用户名@租户名#集群名 String user = "test@xxxx#obtestf"; String password = "xxxxxxx"; Class.forName("com.oceanbase.jdbc.Driver"); Connection connection = DriverManager.getConnection(url, user, password); PreparedStatement pstmt=connection.prepareStatement("update tlob set b=? where id=1"); File f=new File("/tmp/1.dat"); InputStream in=null; in=new FileInputStream(f); pstmt.setBinaryStream(1, in); if(!pstmt.execute()) { System.out.println("pstmt update lob failed!"); }else { connection.commit(); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } --- 使用 dd 命令创建一个大小为 48 MB 的文件 # dd if=/dev/zero of=/tmp/1.dat bs=1024 count=49152 -- or # dd if=/dev/zero of=/tmp/1.dat bs=1M count=48
Note:
使用OceanBase Connector/J 驱动,java可以较快速的写入BLOB,但测试上限也仅为48M,数据库为OB 3.2.4 for oracle租户.
总结
尽管OceanBase在不断进化,增强了对各种数据类型的兼容性和支持度,但在实施迁移项目时,仍然需要详细评估源系统的数据结构和目标系统的限制,以确保迁移过程的顺利进行,并避免潜在的数据损失或应用兼容性问题。在实际迁移场景中,建议进行详细的规划和测试,特别是针对LOB对象的处理,以确保数据完整性和应用兼容性。值得一提的是,国内数据库产品,如OceanBase,正处于快速迭代的阶段。对于一些非常规的功能需求,可能需要用户给予一定的耐心,等待这些数据库系统逐步完善并集成所需特性。不可否认的是,OceanBase在版本更新速度上属于较快的行列。因为目前我们缺少V4版本环境,无法验证LOB在V4中的表现,如果您有环境不妨反馈一下测试结果。
— update 2024/7/11–
— enjoy —
对不起,这篇文章暂时关闭评论。