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

JDBC多线程场景下数据库数据异常问题求助

解决JDBC多线程战斗场景数据异常与并行问题的方案

一、替换本地锁为数据库行级锁+事务

Java进程内的ReentrantLock无法跨连接保证数据库操作的原子性,多线程下容易出现脏读、丢失更新。正确的做法是利用数据库的行级锁和事务来控制:

  • 查询参战Clan时使用SELECT ... FOR UPDATE语句,让数据库锁定目标行,其他线程必须等待当前事务提交后才能操作该行;
  • 将战斗计算、金库更新、Event插入整个流程放在同一个JDBC事务中,设置conn.setAutoCommit(false),所有操作完成后统一commit(),异常则rollback(),避免部分操作生效导致数据不一致。

二、规范JDBC连接与事务边界

  • 每个线程必须使用独立的Connection,不要共享连接;
  • 事务边界要统一在战斗逻辑的最外层(比如ActionService的fight方法),不要在Repository层随意开启/提交事务;
  • 操作完成后务必关闭Connection,避免连接泄漏。

三、用乐观锁实现无锁并行(优化并行度)

如果想减少锁等待、提升多线程并行效率,可采用乐观锁方案:

  • 在Clan表新增version字段,每次更新时校验版本号:UPDATE clan SET gold = ?, version = version + 1 WHERE id = ? AND version = ?;
  • 若更新返回受影响行数为0,说明数据已被其他线程修改,此时可重试战斗逻辑(注意设置重试次数上限,避免死循环)。这种方式无需显式加锁,冲突不频繁时能大幅提升并行能力。

四、异常排查要点

  • 开启数据库SQL日志(如MySQL的general_log),查看SQL执行顺序、事务提交时机,定位是脏读、丢失更新还是部分提交;
  • 检查JDBC事务隔离级别,MySQL默认的REPEATABLE READ已能解决大部分一致性问题,无需随意修改。

代码示例片段

Service层战斗方法

public void fight(Long attackerId, Long defenderId) {
    Connection conn = null;
    try {
        conn = getConnection(); // 每个线程获取独立连接
        conn.setAutoCommit(false);
        
        // 加行级锁查询双方Clan
        Clan attacker = clanRepository.getByIdForUpdate(conn, attackerId);
        Clan defender = clanRepository.getByIdForUpdate(conn, defenderId);
        
        // 执行战斗计算
        int gain = calculateBattleGain(defender);
        attacker.setGold(attacker.getGold() + gain);
        defender.setGold(defender.getGold() - gain);
        
        // 更新金库
        clanRepository.update(conn, attacker);
        clanRepository.update(conn, defender);
        
        // 插入战斗事件
        Event battleEvent = new Event(attackerId, defenderId, gain);
        eventRepository.insert(conn, battleEvent);
        
        conn.commit();
    } catch (SQLException e) {
        if (conn != null) {
            try {
                conn.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }
        throw new RuntimeException("战斗执行失败", e);
    } finally {
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

Repository行级锁查询方法

public Clan getByIdForUpdate(Connection conn, Long id) throws SQLException {
    String sql = "SELECT id, gold FROM clan WHERE id = ? FOR UPDATE";
    try (PreparedStatement stmt = conn.prepareStatement(sql)) {
        stmt.setLong(1, id);
        try (ResultSet rs = stmt.executeQuery()) {
            if (rs.next()) {
                Clan clan = new Clan();
                clan.setId(rs.getLong("id"));
                clan.setGold(rs.getInt("gold"));
                return clan;
            }
            throw new RuntimeException("Clan不存在");
        }
    }
}

乐观锁更新方法(可选)

public boolean updateWithOptimisticLock(Connection conn, Clan clan) throws SQLException {
    String sql = "UPDATE clan SET gold = ?, version = version + 1 WHERE id = ? AND version = ?";
    try (PreparedStatement stmt = conn.prepareStatement(sql)) {
        stmt.setInt(1, clan.getGold());
        stmt.setLong(2, clan.getId());
        stmt.setInt(3, clan.getVersion());
        return stmt.executeUpdate() > 0;
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 09:10:29