使用Oracle Merge与JDBC Template批量更新时偶发插入重复数据问题
我之前也碰到过类似的批量MERGE偶发重复的状况,结合你的场景(SQL单独执行正常、20万条数据里仅150条重复),大概率是并发竞态冲突或者JDBC批量执行的机制特性导致的,给你几个针对性的解决思路:
1. 先加数据库层面的兜底防护:唯一约束
不管业务逻辑怎么优化,数据库层面的唯一约束是最可靠的防线。既然你是用column_one判断重复,直接给这个字段加唯一约束:
ALTER TABLE XXX ADD CONSTRAINT uk_xxx_column_one UNIQUE (column_one);
这样就算后续批量操作因为并发出现漏判,数据库会直接抛出ORA-00001: unique constraint violated异常,你可以在代码里捕获这个异常,做重试或者日志记录,绝对能阻止脏数据入库。
2. 检查JDBC Template批量执行的事务边界
JDBC Template的batchUpdate如果没有被包裹在同一个事务中,每条批量语句可能处于独立的事务里。这种情况下,当两条带相同column_one的记录几乎同时执行MERGE时,各自的事务看不到对方的未提交数据,都会判断为“未匹配”进而触发插入。
解决办法:
- 把整个批量操作放在一个事务里:比如用Spring的
@Transactional注解标记执行批量更新的方法,或者手动通过TransactionTemplate来管理事务。 - 拆分大批次为小批次:如果一次批量20万条数据量太大,可以拆成每500条(或根据实际情况调整)一批,每批在独立事务中执行,减少同一批次内的竞态概率。
3. 优化MERGE语句的批量执行逻辑
你当前用USING dual的MERGE写法,每条语句都是独立判断,批量执行时数据库可能逐条处理,同一批次内的重复记录可能因为判断时机问题都被插入。可以改成一次性批量MERGE多行数据的写法(Oracle 12c+支持):
MERGE INTO XXX t USING ( SELECT ? col1, ? col2, ? col3 FROM dual UNION ALL SELECT ? col1, ? col2, ? col3 FROM dual -- 继续添加批量里的其他行 ) s ON (t.column_one = s.col1) WHEN NOT MATCHED THEN INSERT (column_one, column_two, column_three) VALUES (s.col1, s.col2, s.col3);
这种写法让数据库一次性处理所有批量数据,内部会先对s中的数据做去重判断,避免同一批次内的重复插入。不过需要动态构造SQL,或者用Oracle的临时表、集合类型传入批量数据,适合数据量较大的场景。
4. 代码层面提前去重
在把数据传入批量更新之前,先在内存里做一次去重,从源头减少重复数据进入数据库的可能。比如用HashMap以column_one为key过滤:
// 假设YourData是你的数据实体类 Map<String, YourData> uniqueMap = new HashMap<>(); for (YourData item : originalDataList) { // 可以根据业务规则保留第一次出现的记录,或者最新的记录 if (!uniqueMap.containsKey(item.getColumnOne())) { uniqueMap.put(item.getColumnOne(), item); } } // 转换为JDBC批量需要的参数列表 List<Object[]> batchParams = new ArrayList<>(); for (YourData item : uniqueMap.values()) { batchParams.add(new Object[]{item.getColumnOne(), item.getColumnTwo(), item.getColumnThree()}); } jdbcTemplate.batchUpdate(sql, batchParams);
总结
优先级建议:先加唯一约束兜底,然后在代码层面做内存去重,最后调整事务和MERGE语句的批量写法。这样既能解决当前的偶发重复问题,也能避免后续类似并发场景导致的脏数据。
内容的提问来源于stack exchange,提问作者Chakradhar Vyza

