You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何去除JDBC PreparedStatement批处理中的重复更新操作?

解决JDBC批处理重复更新的去重方案

嘿,这个问题我之前做批处理优化的时候碰到过!核心痛点就是重复的更新请求白白浪费数据库资源,既然你没法修改填充批处理的算法,那咱们可以从输入预处理或者拦截批处理添加操作这两个方向入手,给你分享两个实用的方案:

方案1:先给roas列表去重,再交给原算法处理

这是最直接的思路——既然原算法是按roas列表生成批处理,那我们先把列表里重复的条目干掉,原算法处理的就是干净的数据了。这里分两种情况:

情况A:只需要保留第一次出现的更新

如果同一个(asn, prefix, max_length)组合的更新,不管后续的isWhitelist/isFilter值如何,执行一次就够,那用LinkedHashMap来做去重(还能保留原来的顺序):

首先得写个用来判断重复的Key类,把三个更新条件包起来:

class RoaUpdateKey {
    private final long asn;
    private final Object prefix;
    private final int maxLength;

    public RoaUpdateKey(long asn, Object prefix, int maxLength) {
        this.asn = asn;
        this.prefix = prefix;
        this.maxLength = maxLength;
    }

    // 一定要重写equals和hashCode,不然Map认不出相同的组合
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        RoaUpdateKey that = (RoaUpdateKey) o;
        return asn == that.asn && maxLength == that.maxLength && Objects.equals(prefix, that.prefix);
    }

    @Override
    public int hashCode() {
        return Objects.hash(asn, prefix, maxLength);
    }
}

然后处理roas列表:

// 用LinkedHashMap保证顺序,putIfAbsent只保留第一次出现的条目
Map<RoaUpdateKey, Roa> uniqueRoasMap = new LinkedHashMap<>();
for (Roa roa : roas) {
    RoaUpdateKey key = new RoaUpdateKey(roa.getAsn(), roa.getPrefix(), roa.getMax_length());
    uniqueRoasMap.putIfAbsent(key, roa);
}
// 转成去重后的列表,交给原算法处理
List<Roa> uniqueRoas = new ArrayList<>(uniqueRoasMap.values());

情况B:需要保留最后一次的更新(字段值有变化)

如果同一个更新条件的后续条目里,isWhitelist或isFilter是最新的业务值,那直接用put覆盖之前的条目就行:

Map<RoaUpdateKey, Roa> uniqueRoasMap = new LinkedHashMap<>();
for (Roa roa : roas) {
    RoaUpdateKey key = new RoaUpdateKey(roa.getAsn(), roa.getPrefix(), roa.getMax_length());
    uniqueRoasMap.put(key, roa); // 后面的条目会覆盖前面的,保留最新的
}
List<Roa> uniqueRoas = new ArrayList<>(uniqueRoasMap.values());

方案2:给PreparedStatement加个“去重代理”

如果连roas列表的预处理都做不了,那咱们可以给原PreparedStatement套个壳,拦截addBatch()操作,判断当前参数对应的更新条件是不是已经加过批处理,没加过才真正调用原方法:

class DeduplicatingPreparedStatement implements PreparedStatement {
    private final PreparedStatement delegate;
    private final Set<RoaUpdateKey> processedKeys = new HashSet<>();

    // 用来临时存储当前设置的三个更新条件参数
    private long currentAsn;
    private Object currentPrefix;
    private int currentMaxLength;

    public DeduplicatingPreparedStatement(PreparedStatement delegate) {
        this.delegate = delegate;
    }

    // 重写setXXX方法,记录我们需要的三个参数
    @Override
    public void setLong(int parameterIndex, long x) throws SQLException {
        if (parameterIndex == 1) currentAsn = x;
        delegate.setLong(parameterIndex, x);
    }

    @Override
    public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
        if (parameterIndex == 2) currentPrefix = x;
        delegate.setObject(parameterIndex, x, targetSqlType);
    }

    @Override
    public void setInt(int parameterIndex, int x) throws SQLException {
        if (parameterIndex == 3) currentMaxLength = x;
        delegate.setInt(parameterIndex, x);
    }

    // 其他setXXX方法直接委托给原PreparedStatement就行,这里只列了用到的
    @Override
    public void setBoolean(int parameterIndex, boolean x) throws SQLException {
        delegate.setBoolean(parameterIndex, x);
    }

    // 核心:拦截addBatch,去重后再执行
    @Override
    public void addBatch() throws SQLException {
        RoaUpdateKey key = new RoaUpdateKey(currentAsn, currentPrefix, currentMaxLength);
        // HashSet的add方法返回true表示之前没有这个key,才执行原addBatch
        if (processedKeys.add(key)) {
            delegate.addBatch();
        }
    }

    // 注意:PreparedStatement还有很多其他方法,比如executeBatch、close等,都需要委托给delegate,你可以用IDE自动生成这些方法,或者用CGLIB这类动态代理框架简化代码
}

然后替换原PreparedStatement的创建逻辑:

String updateFilter = "UPDATE payload_roas SET filtered = TRUE WHERE asn = ? AND prefix= ? AND max_length = ?";
PreparedStatement originalPs = connection.prepareStatement(updateFilter);
// 套上去重代理
PreparedStatement ps = new DeduplicatingPreparedStatement(originalPs);

// 原来的填充算法完全不用改,直接用这个ps就行

额外提个性能优化点

除了去重,记得检查你的JDBC驱动配置,比如MySQL的话,一定要开启rewriteBatchedStatements=true,这个参数能把多条批处理语句合并成一条,性能提升非常明显!另外也可以考虑分批次执行(比如每1000条执行一次executeBatch再clearBatch),避免内存占用过高。

内容的提问来源于stack exchange,提问作者RefiPeretz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:29:48