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
相关产品推荐
相关产品推荐

