问题
线上在查比较复杂的SQL是,出现以下报错:
Error querying database. Cause: com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure\n\nThe last packet successfully received from the server was 10,181 milliseconds ago. The last packet sent successfully to the server was 10,239 milliseconds ago.
从报错意思上看,是数据库连接已经被关闭了。经过多次实验确认,只要SQL执行超过10s,就会出现以上错误。
原因
项目是springboot
,数据库连接池用的是阿里的druid-spring-boot-starter-1.2.15
。
如上图所示:JDBC通过socket对字节流进行处理,因此也会有一些基本网络操作,类似于HttpClient这种用于网络操作的代码库;同样的也会受到ConnectTimeout/SocketTime
的影响。DruidDataSource
有设置以上超时的方法:
DruidDataSource dataSource = new DruidDataSource();
dataSource.setConnectTimeout(100_000);
dataSource.setSocketTimeout(100_000);
但是当前系统并没有设置以上参数,那为什么请求超时10s
就会报错?并且当前druid-spring-boot-starter-1.2.15
版本并没有socketTime&connetctTimeout
配置参数(无法通过spring.datasource.druid
前缀来配置)。
通过源码(DruidDatasource类)可以看到以下内容:
public class DruidDataSource extends DruidAbstractDataSource implements DruidDataSourceMBean, ManagedDataSource, Referenceable, Closeable, Cloneable, ConnectionPoolDataSource, MBeanRegistration {
public void init() throws SQLException {
…… // 省略
try {
…… // 省略
if (this.jdbcUrl != null) {
this.jdbcUrl = this.jdbcUrl.trim();
…… // 省略
initFromUrlOrProperties(); // 从url里解析参数
}
if (connectTimeout == 0) {
socketTimeout = DEFAULT_TIME_CONNECT_TIMEOUT_MILLIS; // 10_000
}
if (socketTimeout == 0) {
socketTimeout = DEFAULT_TIME_SOCKET_TIMEOUT_MILLIS; // 10_000
}
…… // 省略
} catch (SQLException e) {
…… // 省略
} finally {
…… // 省略
}
}
}
通过以上源码可以看到,假如没有设置参数值,则会默认设置为10s
。
继续看initFromUrlOrProperties
方法:
public class DruidDataSource extends DruidAbstractDataSource implements DruidDataSourceMBean, ManagedDataSource, Referenceable, Closeable, Cloneable, ConnectionPoolDataSource, MBeanRegistration {
private void initFromUrlOrProperties() {
// 从jdbc的url里解析
if (jdbcUrl.startsWith("jdbc:mysql://")) {
if (jdbcUrl.indexOf("connectTimeout=") != -1 || jdbcUrl.indexOf("socketTimeout=") != -1) {
String[] items = jdbcUrl.split("(\\?|&)");
for (int i = 0; i < items.length; i++) {
String item = items[i];
if (item.startsWith("connectTimeout=")) {
String strVal = item.substring("connectTimeout=".length());
setConnectTimeout(strVal);
} else if (item.startsWith("socketTimeout=")) {
String strVal = item.substring("socketTimeout=".length());
setSocketTimeout(strVal);
}
}
}
// 从配置的connectProperties里解析:spring.datasource.druid.connectionProperties=connectTimeout=50000;socketTimeout=50000;
Object propertyConnectTimeout = connectProperties.get("connectTimeout");
if (propertyConnectTimeout instanceof String) {
setConnectTimeout((String) propertyConnectTimeout);
} else if (propertyConnectTimeout instanceof Number) {
setConnectTimeout(((Number) propertyConnectTimeout).intValue());
}
Object propertySocketTimeout = connectProperties.get("socketTimeout");
if (propertySocketTimeout instanceof String) {
setSocketTimeout((String) propertySocketTimeout);
} else if (propertySocketTimeout instanceof Number) {
setSocketTimeout(((Number) propertySocketTimeout).intValue());
}
}
}
}
可以看到有以下两种方式来配置:
- JDBC的URL:
jdbc:mysql://xxxx?connectTimeout=50000&socketTimeout=50000
。 - 通过配置
spring.datasource.druid.connectionProperties
来设置:spring.datasource.druid.connectionProperties=connectTimeout=50000;socketTimeout=50000;
并且由于initFromUrlOrProperties
方法是在init
方法内,以及propertyConnectTimeout
是在URL
后面解析,可以得出优先级:connectProperties
大于 jdbcUrl
大于 set方法
。