dbms_lob.read的offset和amount单位必须区分clob与blob:clob按字符(offset从1起,amount为字符数),blob按字节;混用导致乱码或ora-24801。

DBMS_LOB.READ 的 offset 和 amount 单位必须分 CLOB/BLOB 判断
读取 LOB 时最常踩的坑是把 CLOB 和 BLOB 当成同一套单位处理,结果乱码或报 ORA-24801。CLOB 的 offset 是从 1 开始的**字符位置**,amount 是要读的**字符数**;BLOB 的 offset 和 amount 全部按**字节**算。
比如数据库字符集是 AL32UTF8:
- “你好”占 2 个字符、6 字节 —— 对 CLOB,
offset := 1; amount := 2读出完整字符串 - 对 BLOB,同样参数会只读前 2 字节(
0xE4BD),截断在 UTF-8 中间,返回乱码 - 若真要读 BLOB 中的“你好”,得先用
UTL_RAW.LENGTH或LENGTHB算出字节长度,再传amount := 6
向空 CLOB/BLOB 写入前必须初始化 locator
声明一个 clob_var CLOB 变量 ≠ 拥有可写的 LOB 实例。它只是空 locator,直接调 DBMS_LOB.WRITE_APPEND 会静默失败,或报 ORA-22285 / ORA-22289。
两种安全初始化路径:
- INSERT 场景:先用
INSERT ... VALUES (..., EMPTY_CLOB(), ...)占位,再SELECT clob_col INTO clob_var FROM t WHERE ... FOR UPDATE锁定行,之后才能写 - 临时 LOB 场景:显式调
DBMS_LOB.CREATETEMPORARY(clob_var, TRUE),第二个参数TRUE表示启用缓存,性能更好 - 切勿对刚 SELECT 出来的 LOB 调
DBMS_LOB.OPEN(..., DBMS_LOB.LOB_READONLY)后再写 —— 只读打开后WRITE_APPEND必报错
DBMS_LOB.CONVERTTOBLOB 不转码,仅字节复制
这个函数名极具误导性。DBMS_LOB.CONVERTTOBLOB 不做任何字符集转换,它只是把 CLOB 当作字节流原样拷进 BLOB。
例如 CLOB 存的是 UTF-8 编码的 XML:“你好” → 0xE4BDA0E5A5BD(6 字节),转出的 BLOB 就是这 6 字节。
如果下游 Java 程序默认按 GBK 解析(“你好”在 GBK 是 0xC4E3BAC3,4 字节),就会显示乱码。
真要转码,得走两步:
- 用
UTL_I18N.STRING_TO_RAW('你好', 'ZHS16GBK')得到 GBK 编码的 RAW - 再用
DBMS_LOB.WRITE或WRITE_APPEND写入目标 BLOB
单次读写不能超 32767 字节,大 LOB 必须分块
VARCHAR2 和 RAW 在 PL/SQL 中上限就是 32767 字节,所以 DBMS_LOB.READ 的 amount 不能硬写成 100000,否则直接报错。
分块逻辑本身不难,但容易漏掉三个细节:
- 每次
amount要 ≤ 32767,且对 CLOB 是字符数、对 BLOB 是字节数 —— 单位混淆会导致最后一块读不全 - CLOB 分块时,
offset累加的是字符数;BLOB 分块时,offset累加的是字节数 —— 混用会跳过或重复读某段 - 循环退出条件不能只看
DBMS_LOB.GETLENGTH返回值,因为 CLOB 长度是字符数、BLOB 是字节数,而你当前读的 buffer 类型决定了你该用哪个维度判断是否读完











