Oracle必须用EMPTY_CLOB()占位再FOR UPDATE锁定,否则INSERT带字符串值到CLOB字段会失败;SQL Server需用.WRITE()分块覆盖;PostgreSQL应使用lo_from_bytea()获取OID后存业务表。

Oracle必须用EMPTY_CLOB()占位再FOR UPDATE锁定
直接INSERT带字符串值到CLOB字段会失败——超4000字节报ORA-01704,传空字符串或NULL则后续DBMS_LOB.WRITEAPPEND会静默失败或报ORA-22275。你不是在插值,而是在分配一个可写的LOB定位器。
正确路径分三步:
-
INSERT INTO t(id, clob_col) VALUES (1, EMPTY_CLOB())—— 必须用EMPTY_CLOB(),不能用''或NULL -
SELECT clob_col INTO :loc FROM t WHERE id = 1 FOR UPDATE—— 缺FOR UPDATE就锁不住行,写入必错 -
DBMS_LOB.WRITEAPPEND(:loc, LENGTH(chunk), chunk)——chunk必须是VARCHAR2,且≤32767字节
SQL Server用.WRITE()分块覆盖,别拼接
表面看UPDATE t SET blob_col = @data能用,但实际极不可靠:客户端传入的varbinary(max)若超网络max_packet_size(默认4MB),会在协议层被静默截断;T-SQL里@a + @b + @c拼接会触发隐式转换,中间结果可能被截为8000字节。
安全写法只有一条:
- 用
UPDATE t SET blob_col.WRITE(@chunk, @offset, LEN(@chunk)) WHERE id = @id -
@offset从0开始,每次写入前需查当前长度(DATALENGTH(blob_col)) - 避免在T-SQL中模拟流式读取——
READTEXT/WRITETEXT已弃用,且跨库不兼容
PostgreSQL用lo_from_bytea()获取OID后存业务表
PostgreSQL的lo(大对象)和bytea是两套机制,混用必错。想存客户端上传的二进制数据,不能用lo_import()(它只认服务端本地路径),得走lo_from_bytea()。
关键点:
-
SELECT lo_from_bytea(0, $1)—— 第一个参数为0表示系统自动分配OID;若指定非零OID,要确保未被占用,否则报duplicate key value violates unique constraint - 返回值类型是
oid,必须存在业务表的OID类型字段里,别用int4接收 - 操作必须在
BEGIN/COMMIT事务块内,PL/pgSQL函数里不会自动包裹事务
分片大小与字符边界容易踩坑
所有数据库的“分片”都不是按任意字节数切,而是受底层单位约束:
- Oracle:
DBMS_LOB.WRITEAPPEND第三个参数是VARCHAR2,最大32767字节;但UTF-8下多字节字符(如中文)可能被截断在中间字节——建议按4000或8000字节切,避开字符边界风险 - SQL Server:
.WRITE()的@offset和@length单位都是字节,但要注意varbinary(max)变量本身在T-SQL中也有隐式长度限制 - PostgreSQL:
lo_get(oid)一次性加载全部内容进内存,>100MB易OOM;真要分片读,只能用lo_open+lo_read在libpq或PL/pgSQL中实现,纯SQL无标准API
最常被忽略的是:Oracle临时CLOB忘了调DBMS_LOB.FREETEMPORARY(),会导致PGA内存持续增长;PostgreSQL没显式COMMIT就调lo_from_bytea(),整个操作直接失败。这些不是警告,是立刻阻断流程的硬性条件。

















