Oracle BLOB写入必须用PreparedStatement.setBinaryStream()而非setBytes(),否则大文件易致OutOfMemoryError;需配合setAutoCommit(false)、EMPTY_BLOB()占位、SELECT...FOR UPDATE加锁,并在同事务内完成写入与提交。

Oracle BLOB写入必须用 PreparedStatement.setBinaryStream(),别用 setBytes()
直接调用 setBytes() 会把整个二进制内容加载进 JVM 内存,大文件(比如 >10MB)极易触发 OutOfMemoryError。Oracle JDBC 驱动对 BLOB 的流式写入支持良好,但前提是用对 API。
关键点:必须配合 Connection.setAutoCommit(false),且在 setBinaryStream() 前确保 BLOB 字段已初始化(通过 EMPTY_BLOB() 或插入后 SELECT ... FOR UPDATE)。
-
setBinaryStream(int parameterIndex, InputStream x, long length)是唯一安全选择;length必须精确(不能传 -1,否则部分驱动会回退到内存缓冲) - 输入流必须支持重复读取(如
FileInputStream可以,但某些网络流或加密流可能不行) - Oracle 12c+ 推荐用
setBinaryStream(int, InputStream, int)的三参数重载(第三个是 int 类型的长度),避免长整型长度在旧驱动中被截断
INSERT 后立即 SELECT ... FOR UPDATE 获取 BLOB 定位器
Oracle 的 BLOB 不是“直接赋值”,而是先占位、再流式填充。常见错误是 INSERT 时用 EMPTY_BLOB() 占位,但没加事务锁,导致后续 SELECT ... FOR UPDATE 报 ORA-01002: fetch out of sequence 或写入失败。
典型流程:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
conn.setAutoCommit(false);
String insertSql = "INSERT INTO doc_table(id, content) VALUES (?, EMPTY_BLOB())";
try (PreparedStatement ps = conn.prepareStatement(insertSql)) {
ps.setString(1, "doc_123");
ps.executeUpdate();
}
// 必须立刻查出刚插入的 BLOB 并加锁
String selectSql = "SELECT content FROM doc_table WHERE id = ? FOR UPDATE";
try (PreparedStatement ps = conn.prepareStatement(selectSql);
ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
Blob blob = rs.getBlob("content");
try (OutputStream os = blob.setBinaryStream(1L); // 从位置 1 开始写
FileInputStream fis = new FileInputStream("/tmp/report.pdf")) {
fis.transferTo(os); // Java 9+
}
}
}
conn.commit();-
FOR UPDATE不能省——没有它,blob.setBinaryStream()会抛SQLException(驱动底层无法定位可写 LOB) -
rs.getBlob()返回的是定位器(locator),不是实际数据;真正写入发生在setBinaryStream()返回的OutputStream上 - 务必在同一个事务内完成 INSERT → SELECT FOR UPDATE → BLOB 写入 → COMMIT,跨事务会导致定位器失效
用 oracle.sql.BLOB 替代标准 java.sql.Blob 可绕过部分驱动限制
标准 JDBC 的 Blob 接口在 Oracle 驱动里是包装类,某些版本(尤其 ojdbc6)对大流写入有缓冲策略缺陷。直接使用 Oracle 特有的 oracle.sql.BLOB 更可控,但需强依赖 ojdbc jar。
操作前确认驱动版本:ojdbc8 支持标准接口已较稳定,ojdbc6/7 建议切换:
// 获取 oracle.sql.BLOB 实例(需 cast)
ResultSet rs = stmt.executeQuery("SELECT content FROM doc_table WHERE id = ? FOR UPDATE");
if (rs.next()) {
oracle.sql.BLOB blob = (oracle.sql.BLOB) rs.getBlob("content");
OutputStream os = blob.getBinaryOutputStream(); // 注意:不是 setBinaryStream()
// ... 写入逻辑
}-
getBinaryOutputStream()比setBinaryStream()更底层,不走 JDBC Blob 包装,吞吐略高 - 该方法要求连接未关闭、事务未提交,且
getBinaryOutputStream()必须在FOR UPDATE查询之后立即调用 - 如果项目用 Spring JDBC 或 JPA,此方式需绕过抽象层,手动获取原生
Connection和Statement
批量写入 BLOB 时,每个 BLOB 必须独立事务或分批处理
一个事务里写 100 个 BLOB?别这么做。Oracle LOB 定位器在事务中持有资源,批量操作极易触发 ORA-01555: snapshot too old 或临时段爆满(ORA-01652)。
实操建议:
- 单次事务控制在 5–10 个 BLOB 以内(取决于平均大小和数据库配置)
- 每个 BLOB 写入后立即
commit()(小数据量场景);或用savepoint分段回滚,避免全盘失败 - 禁用
Connection.prepareStatement()的批处理模式(addBatch()),因为setBinaryStream()无法参与批处理 - 监控
V$TEMPSEG_USAGE和V$SQLAREA中 LOB 相关 SQL 的执行计划,确认是否走了 direct path write
真正卡住的往往不是代码怎么写,而是没意识到 Oracle BLOB 的定位器机制依赖事务上下文和服务器端临时段资源。写之前先看 SELECT * FROM V$SESSION_LONGOPS 里有没有慢 LOB 操作,比反复调优 Java 流更有效。

















