Oracle中addBatch()+executeBatch()易卡住或报错,主因是驱动未启用服务端批量、未分批提交导致游标超限(ORA-01000)或SQL超长(ORA-01795),须设setAutoCommit(false)、setFetchSize(0)、复用PreparedStatement、每500–2000条clearBatch()并commit。

为什么addBatch() + executeBatch()在Oracle里容易卡住或报错
Oracle JDBC驱动对批量操作有特殊行为:默认不启用批量模式,且executeBatch()会把所有语句拼成一条SQL发给数据库(尤其在未显式设置rewriteBatchedStatements=true时),这在Oracle中不生效,反而导致每条语句单独执行、事务开销翻倍。更常见的是触发ORA-01000: maximum open cursors exceeded——因为没及时关闭PreparedStatement或未分批提交。
- 必须显式调用
setFetchSize(0)和setResultSetType(ResultSet.TYPE_FORWARD_ONLY)(虽然对插入无效,但部分Oracle驱动版本会因默认值引发隐式游标泄漏) - 避免在循环内反复
prepareStatement,复用同一个PreparedStatement实例 - 每500–2000条执行一次
executeBatch()并clearBatch(),具体阈值取决于单行数据大小和JVM堆内存
如何正确配置Oracle JDBC连接URL和Statement参数
Oracle不支持MySQL那种rewriteBatchedStatements参数,但依赖两个关键连接属性来降低网络往返和游标压力:useServerPrepStmts=true(确保服务端预编译)和oracle.jdbc.defaultRowPrefetch=100(虽对INSERT影响小,但能减少驱动内部缓冲开销)。同时,PreparedStatement必须设为RETURN_GENERATED_KEYS(如需主键)或NO_GENERATED_KEYS(否则驱动可能额外开游标查序列)。
- 推荐连接URL:
jdbc:oracle:thin:@//host:port/service?useServerPrepStmts=true&oracle.jdbc.defaultRowPrefetch=100 -
PreparedStatement ps = conn.prepareStatement(sql, ResultSet.NO_GENERATED_KEYS)—— 显式禁用生成键,省掉一次DESCRIBE - 务必关闭自动提交:
conn.setAutoCommit(false),并在每批后conn.commit()
遇到ORA-01795: maximum number of expressions in a list is 1000怎么办
这不是JDBC层问题,而是SQL写法踩了Oracle硬限制:IN子句最多1000个元素。批量插入若用INSERT INTO t VALUES (...), (...), ...拼接多值,极易触发此错。Java 8无法靠驱动自动拆分,必须手动切分SQL。
- 不要拼单条含2000个VALUES的INSERT;改用循环中每次拼≤999个VALUES,再
addBatch() - 或彻底放弃多值INSERT,坚持单条INSERT +
addBatch()——虽然网络包多,但稳定、可控、不越界 - 若用MyBatis等框架,确认其
foreach未生成超长IN列表;可改用INSERT ALL语法(需手写,且Oracle 12c+才支持RETURNING)
实际代码里最容易漏掉的三件事
万级数据下,漏掉任意一项都可能导致OOM、游标耗尽或事务锁表。
立即学习“Java免费学习笔记(深入)”;
- 没在
executeBatch()后立即调用ps.clearBatch()—— 下一批会叠加旧语句,内存持续增长 - 没在finally块中显式
ps.close()和conn.close(),尤其发生异常时,连接和预编译语句残留 - 没设置JVM参数:
-XX:MaxMetaspaceSize=256m(防大量PreparedStatement类加载溢出)和-Xmx2g(确保足够堆存批处理缓冲)
Oracle批量插入不是“调对API就行”,关键在控制游标生命周期、拆分节奏和资源释放时机。哪怕只差一个clearBatch(),跑完10万条也可能发现数据库游标数飙到上千。


















