ora-22920 错误源于未加 for update 就写 clob,必须先 select ... for update 锁定行,再调用 dbms_lob.writeappend;分段写入需控制每次 ≤32767 字符,临时 clob 必须显式 freetemporary。

ORA-22920 错误:没加 FOR UPDATE 就写 CLOB?
直接对 CLOB 字段执行 UPDATE 或在 PL/SQL 中调用 DBMS_LOB.WRITEAPPEND 却没先锁定行,必然报 ORA-22920: row containing the LOB value is not locked。这不是权限或配置问题,是 Oracle 的硬性事务约束。
必须在同一个事务内完成两步:
- 先执行 SELECT clob_col INTO :lob_var FROM t WHERE id = :id FOR UPDATE
- 再调用 DBMS_LOB.WRITEAPPEND 或其他写操作
-
FOR UPDATE不能省略,也不能换成FOR UPDATE NOWAIT(除非你明确要跳过阻塞) - 若表启用了分区或物化视图,需确认执行计划是否真实命中目标行——
FOR UPDATE可能静默失效 - 在存储过程中,
SELECT ... FOR UPDATE必须与后续DBMS_LOB操作处于同一事务块,不能跨COMMIT
DBMS_LOB.WRITEAPPEND 分段写入必须控制长度
DBMS_LOB.WRITEAPPEND 每次最多接受 VARCHAR2(32767) 字节,超长直接报 ORA-06502。这不是性能瓶颈,而是 Oracle 对 PL/SQL 变量类型的底层限制。
正确做法是循环切片:
- 用
DBMS_LOB.GETLENGTH获取源字符串总字符数(注意:CLOB 的 length 是字符数,非字节数) - 每次取
SUBSTR(src, offset, 32767),offset从 1 开始,每次递增 32767 - 传入
DBMS_LOB.WRITEAPPEND(l_clob, LENGTH(part), part)—— 第二个参数必须是part的实际长度,不能硬写 32767 - 避免用
DBMS_LOB.WRITE替代:它需要指定起始位置,容易覆盖已有内容
临时 CLOB 忘记 FREETEMPORARY?PGA 内存就 leak 了
调用 DBMS_LOB.CREATETEMPORARY(l_clob, TRUE) 创建的 CLOB 不会自动回收。高频调用下 PGA 内存持续上涨,最终触发 ORA-04030,且问题不报错、只缓慢恶化。
关键动作必须成对出现:
- 创建时第二个参数必须为
TRUE(启用缓存),否则后续DBMS_LOB.GETLENGTH返回 0 - 写完后必须显式调用
DBMS_LOB.FREETEMPORARY(l_clob) - 异常路径也要兜底:在
EXCEPTION块里补上FREETEMPORARY,不能依赖会话结束 - 临时 CLOB 不能直接用于
INSERT INTO t(clob_col) VALUES (:l_clob)—— 必须先COPY到表中已存在的 LOB 句柄
CLOB 和 BLOB 的 offset/amount 单位完全不同
混用 CLOB/BLOB 的读写逻辑是乱码和 ORA-24801 的主因。单位规则强制区分:
-
CLOB:offset 从 1 开始,是**字符位置**;amount 是要读/写的**字符数**(如 AL32UTF8 下,“你好”算 2 字符,占 6 字节) -
BLOB:offset 和 amount 都是**字节位置和字节数**;若内容是 UTF-8 文本,必须用LENGTHB查字节长度再传入 - 单次读取不能超过 32767 字节(
VARCHAR2上限),大内容必须分批读到变量再拼接 -
DBMS_LOB.CONVERTTOBLOB不转码,只是原样拷贝字节流——CLOB 存的是 UTF-8,转出的 BLOB 就是 UTF-8,下游若期望 GBK 编码,必然乱码
最常被忽略的点:临时 CLOB 的生命周期完全由显式调用控制,漏掉一次 FREETEMPORARY,下次执行可能因 PGA 不足直接失败,但不会立刻报错。











