Java执行SELECT查询时遇ResultSet is from UPDATE异常(高负载场景)
在Java中调用executeQuery方法执行SELECT查询时,明明执行的是SELECT语句,却抛出SQLException,错误提示为:
ResultSet is from UPDATE. No Data.
该问题仅在高负载场景下出现,低负载时查询可正常运行。当前使用mysql connector 8.0.16,目前只能通过重启服务器临时解决,需要在代码层面处理以保障系统稳定性。
示例函数代码如下:
public CacheKey getResult(String type, String colId) { ResultSet resultSet = null; PreparedStatement statement = null; CacheKey returnEntry = null; // 补充原代码遗漏的变量声明 try { String request = "select * from Table where a = ?"; // mCxnContainer是类级别声明的连接对象 statement = mCxnContainer.getStatement(request); if (statement == null) { mCxnContainer = getNewConnection(); statement = mCxnContainer.getStatement(request); } statement.setString(1, colId); resultSet = statement.executeQuery(); returnEntry = cacheEntry.toEntry(resultSet); } catch (SQLException e) { e.printStackTrace(); } finally { try { if (resultSet != null) resultSet.close(); if (statement != null) statement.close(); } catch (SQLException e) { e.printStackTrace(); } } return returnEntry; }
核心原因
这个问题大概率是连接池/共享连接被污染导致的:高负载下,同一个数据库连接被复用,之前的UPDATE/INSERT/DELETE操作未正确清理资源,导致后续SELECT请求复用连接时,驱动认为当前结果集来自更新操作而非查询。另外mysql connector 8.0.16存在部分连接复用相关的bug,也可能触发该问题。
代码层面修复方案
1. 避免类级别共享连接,改用方法内独立管理
原代码中mCxnContainer是类级别共享的连接对象,高负载下多线程并发会导致连接被交叉使用,直接引发资源污染。应改为每次请求获取独立连接,用完即释放:
public CacheKey getResult(String type, String colId) { ResultSet resultSet = null; PreparedStatement statement = null; CacheKey returnEntry = null; Connection conn = null; // 方法内独立管理连接 try { String request = "select * from Table where a = ?"; // 每次请求获取新连接或从连接池取独占连接 conn = getNewConnection(); statement = conn.prepareStatement(request); statement.setString(1, colId); resultSet = statement.executeQuery(); returnEntry = cacheEntry.toEntry(resultSet); } catch (SQLException e) { e.printStackTrace(); // 异常时判断是否为连接问题,尝试重试 if (isConnectionError(e)) { return retryQuery(type, colId); } } finally { // 按顺序关闭资源:ResultSet → Statement → Connection try { if (resultSet != null) resultSet.close(); if (statement != null) statement.close(); if (conn != null) conn.close(); // 必须关闭连接归还到池 } catch (SQLException e) { e.printStackTrace(); } } return returnEntry; } // 判断是否为连接相关异常的辅助方法 private boolean isConnectionError(SQLException e) { String msg = e.getMessage(); return msg != null && (msg.contains("ResultSet is from UPDATE") || msg.contains("Connection closed") || msg.contains("Lost connection")); } // 重试逻辑(限制次数避免死循环) private CacheKey retryQuery(String type, String colId) { int retryTimes = 2; while (retryTimes-- > 0) { try { return getResult(type, colId); } catch (Exception e) { e.printStackTrace(); } } return null; }
2. 升级mysql connector版本
8.0.16版本存在已知的连接复用bug,建议升级到8.0.28及以上稳定版本,官方已修复部分高负载下的连接状态异常问题。
3. 获取连接时强制校验有效性
如果必须使用连接池,在获取连接时强制校验连接状态,避免使用已污染的连接:
// 获取连接时添加有效性检查 public Connection getNewConnection() throws SQLException { Connection conn = yourConnectionPool.getConnection(); // 执行简单查询校验连接是否正常 try (Statement stmt = conn.createStatement()) { stmt.executeQuery("SELECT 1"); } catch (SQLException e) { conn.close(); return yourConnectionPool.getConnection(); // 重新获取可用连接 } return conn; }
4. 不要自定义缓存PreparedStatement
原代码中mCxnContainer.getStatement(request)可能是自定义缓存了PreparedStatement,高负载下多线程共用会导致参数交叉覆盖、结果集污染,应改为每次请求创建新的PreparedStatement,或使用连接池自带的PreparedStatement缓存机制。
内容的提问来源于stack exchange,提问作者Prakash Rastapur

