记录一下由于springboot版本升级引发的java.sql.SQLRecoverableException: IO 错误: Socket read time

803 阅读4分钟

背景

最近公司要在原有的项目上要分出一个子项目,于是领导就把原有的项目框架拷贝了一份并且做了一些框架上的版本升级。然后就是需要把原来的一部分业务代码移植到新的项目上,移植完测试都没有问题于是发布到测试环境,过了两天一看有一些定时获取数据的接口数据没有同步,检查日志后发现在原有的项目上跑的正常的代码在新的项目上出现了报错,拿到参数在本地请求了一下接口发现请求时间很长然后控制台出现了异常信息:


java.sql.SQLRecoverableException: IO 错误: Socket read timed out
	at oracle.jdbc.driver.T4CPreparedStatement.executeForDescribe(T4CPreparedStatement.java:792) ~[ojdbc7-12.1.0.2.jar:12.1.0.1.0]
	at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:897) ~[ojdbc7-12.1.0.2.jar:12.1.0.1.0]
	at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1034) ~[ojdbc7-12.1.0.2.jar:12.1.0.1.0]
	at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3820) ~[ojdbc7-12.1.0.2.jar:12.1.0.1.0]
	at oracle.jdbc.driver.OraclePreparedStatement.execute(OraclePreparedStatement.java:3923) ~[ojdbc7-12.1.0.2.jar:12.1.0.1.0]
	at oracle.jdbc.driver.OraclePreparedStatementWrapper.execute(OraclePreparedStatementWrapper.java:1385) ~[ojdbc7-12.1.0.2.jar:12.1.0.1.0]
	at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:483) [druid-1.2.15.jar:?]
	

一看报错发现错误原因是数据库请求超时,第一反应是数据库配置有问题,公司使用的Oracle 数据库, 使用的数据源是 com.alibaba.druid.pool.DruidDataSource。 打开配置文件和原有项目比对检查了几遍都没问题,配置文件也没被修改过,其他接口都没问题,只有这个出现请求超时。

然后跟着错误信息检查代码后发现mybatis里写的sql查询语句一条sql链接了7个表sql代码有100多行,查看上传日期发现代码上传了快一年了中间基本上也没有什么改动。

1676531841609.png

然后将sql复制倒navicat中进行了查询,好家伙查询了41条数据用时50多秒。

image.png

自己本地添加索引和拆解表链接优化了一下,估计之前写代码的人图省事多连接了很多没用的大表。查询一看速度基本优化到了秒级,将优化完后sql放在mybatis中请求一看没有错误消息了数据可以正常访问。

问题一下就明朗了由于查询速度很慢所以导致链接超时,但是这个sql在另一个项目上是可以正常运行的但是在新的项目上就报错,然后虽然优化了速度但是有的字段还是需要在修改代码进行拼接,这一个改好了其他sql也可能出现问题到时候还需要优化,百度搜了一下发现这个问题很少按照为数不多的解答发现使用了他们的方法之后还是重复出现错误。

然后自己通过比对druid版本后发现之前项目使用的是1.1.3新项目使用的是1.2.15然后在github上找到druid后点开Releases后查看版本说明,果然找到在1.2.12发布的版本中链接池默认增加了配置connectTimeout和socketTimeout默认配置都是10秒,默认值会减少因为网络丢包时导致的连接池无法创建链接。

image.png

对mysql、oracle、postgresql数据库驱动配置连接超时时间,在连接的时候生效,默认值:10000ms com.alibaba.druid.pool.DruidDataSource

if (this.connectTimeout == 0) {
    this.socketTimeout = 10000;
}

if (this.socketTimeout == 0) {
    this.socketTimeout = 10000;
}

打开druid源码通过对比发现com.alibaba.druid.pool.DruidAbstractDataSource中新增了如下代码

if (this.connectTimeout > 0) {
    if (this.isMySql) {
        if (this.connectTimeoutStr == null) {
            this.connectTimeoutStr = Integer.toString(this.connectTimeout);
        }

        physicalConnectProperties.put("connectTimeout", this.connectTimeoutStr);
    } else if (this.isOracle) {
        if (this.connectTimeoutStr == null) {
            this.connectTimeoutStr = Integer.toString(this.connectTimeout);
        }

        physicalConnectProperties.put("oracle.net.CONNECT_TIMEOUT", this.connectTimeoutStr);
    } else if (this.driver != null && "org.postgresql.Driver".equals(this.driver.getClass().getName())) {
        if (this.connectTimeoutStr == null) {
            this.connectTimeoutStr = Integer.toString(this.connectTimeout);
        }

        physicalConnectProperties.put("loginTimeout", this.connectTimeoutStr);
        if (this.socketTimeoutSr == null) {
            this.socketTimeoutSr = Integer.toString(this.socketTimeout);
        }

        physicalConnectProperties.put("socketTimeout", this.socketTimeoutSr);
    }
}
设置超时的意义:

当数据库出现宕机或网络异常时,jdbc 驱动的 socket 超时是必须的。由于TPC/IP 的结构,socket 没有办法检测到网络错误,因此应用也不能检测到与数据库之间的连接是否已经断开。如果没有设置 socket 超时,应用程序会一直等待数据库返回结果。为了避免死连接,socket 必须设置超时时间,通过设置超时时间可以防止出现网络错误时一直等待的情况并缩短故障时间。

一般的数据库连接池都会提供链接检查的功能,但对于已经在使用中的连接往往不会再进行检测。

查看完文档后在配置文件中添加如下配置

#建立连接时连接超时时间,默认:10000ms
spring.datasource.druid.connect-timeout=60000
#数据库操作超时时间,默认:10000ms
spring.datasource.druid.socket-timeout=60000

YML配置

spring:
  datasource:
    druid:
      connect-timeout: 60000
      socket-timeout: 60000

重复服务后,在断点处发现超时时间已经被配置文件覆盖查询时间长的sql也不会出现错误

image.png

mysql设置超时时间方法

jdbc:mysql://xxx.xx.xxx.xxx:3306/database?connectTimeout=60000&socketTimeout=60000