HikariCP 与 SQLTimeoutException

标签: 框架/架构 HiKariCP SQLTimeoutException | 发表时间:2022-08-27 11:41 | 作者:coderbee
出处:https://coderbee.net

最近碰到一个问题:项目的数据库连接池使用的是 HkiariCP,对每个 SQL 语句的执行超时时间设置为 30秒,结果有个 SQL 超时了,抛出异常 SQLTimeoutException,应用层回滚事务时抛出了连接已关闭的异常。但事实上事务却提交了。

写了个简单的代码来模拟生产场景:
在 Spring 的声明式事务内,有一个 insert 操作,然后是一个 update 操作,在数据库客户端执行 select for update 把要更新的行锁住,这样 update 操作就会超时。

多次调试发现 HikariCP 在碰到 SQL 异常时有个检查机制,满足特定条件的异常会直接关闭底层数据库连接,Spring 拿到的是连接的代理,由于连接已关闭,自然没法回滚事务,会碰到连接已关闭异常。

HikariProxyPreparedStatement

  public int executeUpdate() throws SQLException {
    try {
        return super.executeUpdate();
    } catch (SQLException var2) {
        throw this.checkException(var2);
    }
}

final SQLException checkException(SQLException e)
{
  return connection.checkException(e);
}

ProxyConnection

  final SQLException checkException(SQLException sqle)
{
  boolean evict = false;
  SQLException nse = sqle;
  final SQLExceptionOverride exceptionOverride = poolEntry.getPoolBase().exceptionOverride;
  for (int depth = 0; delegate != ClosedConnection.CLOSED_CONNECTION && nse != null && depth < 10; depth++) {
     final String sqlState = nse.getSQLState();
     if (sqlState != null && sqlState.startsWith("08")
         || nse instanceof SQLTimeoutException
         || ERROR_STATES.contains(sqlState)
         || ERROR_CODES.contains(nse.getErrorCode())) {

        if (exceptionOverride != null && exceptionOverride.adjudicate(nse) == DO_NOT_EVICT) {
           break;
        }

        // broken connection
        evict = true;
        break;
     }
     else {
        nse = nse.getNextException();
     }
  }

  if (evict) {
     SQLException exception = (nse != null) ? nse : sqle;
     LOGGER.warn("{} - Connection {} marked as broken because of SQLSTATE({}), ErrorCode({})",
        poolEntry.getPoolName(), delegate, exception.getSQLState(), exception.getErrorCode(), exception);
     leakTask.cancel();
     poolEntry.evict("(connection is broken)");  // 这里由异步线程来关闭底层的物理连接
     delegate = ClosedConnection.CLOSED_CONNECTION; // 连接代理被替换为已关闭的,Spring 自然无法回滚事务
  }

  return sqle;
}

那么还有个问题,为什么连接关闭会提交事务,得看看 JDBC Connection 的文档,有如下的特别说明:连接关闭时,事务是提交还是回滚取决于时具体的实现,毕竟 JDBC 只是一个规范、上层 API 。
https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html#close–

  It is strongly recommended that an application explicitly commits or rolls back an active transaction prior to calling the 
close method.  If the 
close method is called and there is an active transaction, the results are implementation-defined. 

那么怎么由上层应用来决定是提交还是回滚事务呢,从 checkException 方法可以发现,如果配置一个 SQLExceptionOverride 实现类且其方法 adjudicate(nse) 返回 DO_NOT_EVICT 就不会直接关闭底层连接。

我们可以定制一个 SQLExceptionOverride 实现如下,然后通过配置属性 exceptionOverrideClassName 来指定。

  public class MyExceptionOverride implements SQLExceptionOverride {
    public Override adjudicate(SQLException sqlException) {
        // HikariCP 碰到 SQLException 不直接关闭底层连接,由上层应用来决定。
        return Override.CONTINUE_EVICT;
    }
}

欢迎关注我的微信公众号: coderbee笔记

相关 [hikaricp sqltimeoutexception] 推荐:

HikariCP 与 SQLTimeoutException

- - coderbee笔记
最近碰到一个问题:项目的数据库连接池使用的是 HkiariCP,对每个 SQL 语句的执行超时时间设置为 30秒,结果有个 SQL 超时了,抛出异常 SQLTimeoutException,应用层回滚事务时抛出了连接已关闭的异常. 写了个简单的代码来模拟生产场景:. 在 Spring 的声明式事务内,有一个 insert 操作,然后是一个 update 操作,在数据库客户端执行 select for update 把要更新的行锁住,这样 update 操作就会超时.

关于数据库连接池大小 · brettwooldridge/HikariCP Wiki · GitHub

- -
Brett Wooldridge编辑了此页面 on 8 Jan 2017 ·  29个修订. 开发人员经常会错误地配置连接池. 在配置池时,需要理解一些原则,对于某些原则可能是违反直觉的. 10,000个同时前端用户. 想象一下,您有一个网站,尽管它可能不是Facebook规模,但仍然经常有10,000个用户同时发出数据库请求-每秒约有20,000个事务.

裴东辉-Spring集成HikariCP(另外一款高性能的 JDBC 连接池) - 裴东辉

- - 博客园_首页
HikariCP简介( http://brettwooldridge.github.io/HikariCP/):. 一、项目整体布局(为了区分HikariCP依赖的jar包,jar包都以Hikari-1.3.8-为前缀). 二、测试的类,HikariCPSpring.java. 三、日志打印文件,log4j.properties.