前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >OB 运维 | 一则 JDBC 访问 LOB 类型数据报错案例

OB 运维 | 一则 JDBC 访问 LOB 类型数据报错案例

作者头像
爱可生开源社区
发布2025-01-17 19:26:45
发布2025-01-17 19:26:45
8600
代码可运行
举报
运行总次数:0
代码可运行
作者:任仲禹,爱可生数据库工程师,擅长故障分析和性能优化。

你是否有通过 JDBC 访问 OceanBase(Oracle 模式)的 LOB 类型数据?那大概率会报错。

1故障背景

在某客户现场做 OceanBase 测试时,遇到一则报错如下。

代码语言:javascript
代码运行次数:0
复制
(conn=499974) ORA-06502: PL/SQL: numeric or value error: no data found at oceanbase.DBMS_LOB.READ , line : 162, col : 1

该测试任务在报错的情况下能正常跑完(不阻塞任务流程),但经过业务侧反馈确认,若任务存在报错,则该笔交易会标识为失败。

环境和版本

  • OceanBase(Oracle 模式):4.2.5
  • OceanBase-Client:2.4.12

2排查过程

寻找报错 SQL

我们通过 SQL_AUDIT 可以找到相关的查询语句,再通过报错的时间点去过滤下 gv$ob_sql_audit 视图。

代码语言:javascript
代码运行次数:0
复制
select 
/*+ parallel(16) */
usec_to_time(request_time),svr_ip,sid,ret_code,retry_cnt,trace_id,plan_id,plan_type,elapsed_time,queue_time,execute_time,affected_rows,partition_cnt,query_sql 
from gv$ob_sql_audit 
where 
request_time > '2024-xx-xx 09:00:00' 
and request_time < '2024-xx-xx 09:10:00' 
and ret_code <> 0;

这里获取到报错 SQL 信息:

  • query_sql:call DBMS_LOB.READ('666666F777',32767,15008,NULL);
  • ret_code:-5677

分析 OBServer 日志

通过 trace_id 去 OBServer 日志查到报错信息:

  • [errorcode=-5677] offset is larger than lob_len (ret=-5677, offset_len=15008, lob_len=15007)

传入的第 3 个参数,超出了 OB 中 lob 数据值得限定范围。

通过日志可知,反馈给业务端的报错确实为 CALL DBMS_LOB.READ() 语句导致,但是开发侧表示业务代码并不会执行该 SQL???

所以我们继续寻找报错原因。

获取 SQL 执行同 Session 上下文

继续通过报错的 SQL,在 Audit 中通过 SID(Session ID),查找这个会话上下文相关 SQL,分析是否因业务 SQL 执行导致。

代码语言:javascript
代码运行次数:0
复制
select 
/*+ parallel(16) */
usec_to_time(request_time),svr_ip,sid,ret_code,retry_cnt,trace_id,plan_id,plan_type,elapsed_time,queue_time,execute_time,affected_rows,partition_cnt,query_sql 
from gv$ob_sql_audit 
where 
request_time > '2024-xx-xx 09:00:00' 
and request_time < '2024-xx-xx 09:10:00' 
and sid = xxxxx;

这里找到了关键 SQL!

代码语言:javascript
代码运行次数:0
复制
select * from ACT_xxxxx WHERE xxxxxx_ID = ? ORDER BY NAME_ ASC;

这条语句需要获取 BLOB 对象所在字段 BYTES_,存储 BYTES_ 字段的值大小是 15007,这就跟 OBServer 日志中的报错能对应上。

call DBMS_LOB.READ('666666F777',32767,15008,NULL);

这里的 offset(15008) 大于 LOBMAXSIZE(实际 LOB 数据的存储的最大长度,这里业务表查出来是 15007),所以触发了异常。具体参考[1]

3原因分析

谁发的 CALL DBMS_LOG.READ 语句?

因为业务侧否认代码/框架中直接调用了 CALL DBMS_LOG.READ 语句,我们先推测该 SQL 是 OceanBase 的 JDBC 驱动触发的。

根据业务报错“no data found”搜索 JDBC 2.4.12 源码 (com/oceanbase/jdbc/Blob.java)。

通过源码可知,业务侧执行的 SQL 是查询业务表获取 BLOB 字段数据 select * from ACT_xxxxx WHERE xxxxx_ID = ? ORDER BY NAME_ ASC;,但需要通过调用 JDBC 驱动中的 readFromServer 的方法去获取 BLOB 数据,正是该方法下发了 call DBMS_LOB.READ() 语句

readFromServer 方法

该方法会从数据库服务器读取 BLOB 数据,并将其存储在 BLOB 对象的 data 字段中。通过一个无限循环重复调用存储过程取读取数据,直到读取完毕或遇到异常。

在处理数据时,它会检查数据长度是否超过了 maxLength,并处理可能的字符编码问题。如果遇到 "no data found" 异常,它会认为读取已经完成。

为什么 OFFSET 传值是 15008?

第一次 offset 是1,最后一次是 15008 (超出了业务实际字段长度值 15007 一个字节),这里的越界是符合预期的。该驱动版本中,代码逻辑上会多读一次,最后一次报错后(no data found),就不继续读取。

如何避免报错“no data found”?

通过 JDBC 获取 BLOB 数据有两种方式(oceanbase-client 2.4.7 版本引入了 useLobLocatorV2 参数):

代码语言:javascript
代码运行次数:0
复制
/* 1066 */   USE_LOB_LOCATOR_V2("useLobLocatorV2", Boolean.TRUE, "2.4.7", "Set to true to use lob v2, false to use the original lob", false),

当 JDBC 的参数 useLobLocatorV2=true (默认为 TRUE)时,要查询 LOB 类型业务数据时,驱动先给你返回一个 lob locator(一般不携带实际数据),然后要读实际数据时,驱动再调用 readFromServer 方法发一个 dbms_lob.read 去读实际数据。

当 JDBC 的参数 useLobLocatorV2=false 时,不走上述逻辑,直接通过 StringReader 返回业务数据。

验证

实际客户场景,设置 useLobLocatorV2=false 后,报错问题解决。

4总结

报错问题实际可以忽略,目前是符合驱动预期的行为,不影响业务正常读写使用。

如果业务侧不想返回报错,可以将 JDBC 的 options 加上 useLobLocatorV2=false

设置为 true 的目的

  1. 兼容 Oracle。
  2. 性能上更好。不带数据的 lob locator 会比较轻量一些,如果对一个很大的 LOB做一些操作,比如只读其中部分数据效率更高;如果直接返回全部数据,会有额外的开销。

参考资料

[1]

DBMS_LOB READ 的异常情况: https://www.oceanbase.com/docs/enterprise-oceanbase-database-cn-10000000000886801

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-01-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱可生开源社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1故障背景
    • 环境和版本
  • 2排查过程
    • 寻找报错 SQL
    • 分析 OBServer 日志
    • 获取 SQL 执行同 Session 上下文
  • 3原因分析
    • 谁发的 CALL DBMS_LOG.READ 语句?
      • readFromServer 方法
    • 为什么 OFFSET 传值是 15008?
    • 如何避免报错“no data found”?
    • 验证
  • 4总结
    • 设置为 true 的目的
    • 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档