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

InnoDB锁读至写完成:双服务器目录读写数据不一致修复方案咨询

问题根源

当前代码中的synchronized关键字仅能保证单个JVM进程内的读写互斥,但两台服务器是独立进程,各自持有独立的数据库连接,因此本地同步完全无法解决跨节点的数据一致性问题。问题本质是数据库层面的读写一致性未得到保障,导致服务器B读取到未更新的旧数据。

便捷修复方案

方案1:使用数据库行级锁强制读取最新提交数据

修改读操作的SQL语句,添加行级共享锁,确保读取时等待写入事务提交,直接获取最新数据:

private String readDirectory(String entry, Path directory) {
  // 移除本地synchronized,改用数据库锁实现跨节点同步
  String selectSql = "SELECT directory FROM entries WHERE path = ? FOR SHARE";
  try (PreparedStatement selectStatement = connection.prepareStatement(selectSql)) {
    selectStatement.setString(1, entry);
    try (ResultSet rs = selectStatement.executeQuery()) {
      if (rs.next()) {
        return PathUtils.parseDigest(rs.getString("directory"));
      }
    }
  } catch (SQLException e) {
    throw new RuntimeException(e);
  }
  return NO_DIRECTORY;
}
  • 说明:FOR SHARE(适配MySQL等数据库)会为查询行添加共享锁,写入操作的排他锁会等待共享锁释放,反之读操作也会等待写入事务提交后执行,确保读取最新数据。

方案2:调整事务隔离级别为READ COMMITTED

将读操作的事务隔离级别设置为READ COMMITTED,确保每次读取都能获取其他事务已提交的最新数据:

private String readDirectory(String entry, Path directory) {
  try {
    // 设置事务隔离级别为READ COMMITTED
    connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
    String selectSql = "SELECT directory FROM entries WHERE path = ?";
    try (PreparedStatement selectStatement = connection.prepareStatement(selectSql)) {
      selectStatement.setString(1, entry);
      try (ResultSet rs = selectStatement.executeQuery()) {
        if (rs.next()) {
          return PathUtils.parseDigest(rs.getString("directory"));
        }
      }
    }
  } catch (SQLException e) {
    throw new RuntimeException(e);
  }
  return NO_DIRECTORY;
}
  • 说明:部分数据库(如MySQL InnoDB)默认隔离级别为REPEATABLE READ,该级别下会使用快照读,可能读取到事务启动时的旧数据。改为READ COMMITTED后,每次查询都会读取最新的已提交数据。

方案3:针对文件型数据库(如SQLite)强制写入刷新

如果使用SQLite这类文件存储数据库,需确保写入操作提交后立即刷新到磁盘,避免另一台服务器读取文件缓存:

private void writeDirectory(String entry, Path directory) {
  String pathString = PathUtils.toString(directory);
  String insertSql = "INSERT or REPLACE INTO entries (path, directory) VALUES (?,?)";
  try {
    connection.setAutoCommit(false); // 关闭自动提交,手动控制事务
    try (PreparedStatement insertStatement = connection.prepareStatement(insertSql)) {
      insertStatement.setString(1, entry);
      insertStatement.setString(2, pathString);
      insertStatement.executeUpdate();
      connection.commit(); // 提交事务
      // 强制SQLite刷新数据到磁盘
      try (Statement stmt = connection.createStatement()) {
        stmt.execute("PRAGMA synchronous = FULL");
      }
    } finally {
      connection.setAutoCommit(true); // 恢复自动提交默认配置
    }
  } catch (SQLException e) {
    throw new RuntimeException(e);
  }
}
  • 说明:文件型数据库可能将写入操作缓存到内存,未立即同步到磁盘,导致其他服务器读取文件时仍获取旧数据。通过手动提交事务+强制同步的方式,确保写入数据持久化。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 02:50:24