1. 前言
connectTimeout
: 连接超时
loginTimeout
: 登录超时
socketTimeout
: Socket网络超时,即读超时
queryTimeout
: sql执行超时
transactionTimeout
:spring事务超时
innodb_lock_wait_timeout
:innodb锁等待超时
netTimeoutForStreamingResults
:mysql server网络回包写超时(针对大量数据查询的sql)
2 connectTimeout和loginTimeout
mysql数据库在建立连接时,会在connectTimeout 、loginTimeout这两个变量中的取其之一作为真正的连接超时属性,具体取值逻辑是在com.mysql.cj.protocol.StandardSocketFactory#connect
建立连接时调用的getRealTimeout
方法。
getRealTimeout方法的expectedTimeout参数值是connnectTimeout.
getRealTimeout的逻辑是如果loginTimeout有值(this.loginTimeoutCountdown > 0)且[connnectTimeout没值(this.loginTimeoutCountdown > 0)或connnectTimeout值大于loginTimeout]则取值loginTimeout,否则取值connnectTimeout。也就是说这个方法取值思路是:两者都有值时,在两者中取较小的那个值作为最终的连接超时时间,两者中只有一个有值时,取有值那个参数作为最终的连接超时时间。
既然说到这儿了,那么我们应该搞清楚connnectTimeout
loginTimeout
这两个参数的来源是在哪儿?
1) loginTimeout
loginTimeout参数来源于驱动管理器的loginTimeout
,在com.mysql.cj.jdbc.ConnectionImpl#connectOneTryOnly
方法中可以看到这个取值逻辑。
貌似我们没有给驱动管理器设置过登录超时这参数,DriverManager#loginTimeout的默认值是0,不应该是30。
其实这DriverManager#loginTimeout现在的值是HikariCP连接池给我们设的默认值。HikariPool构造方法中初始化执行PoolBase#initializeDataSource
时调用setLoginTimeout
去给DriverManager设置登录超时
上面PoolBase#setLoginTimeout(DataSource)
方法中的dataSource
参数是com.zaxxer.hikari.util.DriverDataSource
类的实例,而com.zaxxer.hikari.util.DriverDataSource#setLoginTimeout(int)
方法就是会直接给DriverManager的loginTimeout
设值。
//com.zaxxer.hikari.util.DriverDataSource
@Override
public void setLoginTimeout(int seconds) throws SQLException
{
DriverManager.setLoginTimeout(seconds);
}
从下面的代码可以看出,PoolBase#connectionTimeout
属性值来源于HikariConfig#connectionTimeout
,而HikariConfig#connectionTimeout
的属性值又来源于配置文件中的spring.datasource.hikari.connection-timeout
属性值,若配置文件中的此属性值为空,则取默认值30秒
PoolBase(final HikariConfig config)
{
this.config = config;
//....
//PoolBase#connectionTimeout来自HikariConfig#connectionTimeout
this.connectionTimeout = config.getConnectionTimeout();
this.validationTimeout = config.getValidationTimeout();
this.lastConnectionFailure = new AtomicReference<>();
//....
initializeDataSource();
}
public class HikariConfig implements HikariConfigMXBean
{
private static final Logger LOGGER = LoggerFactory.getLogger(HikariConfig.class);
private static final char[] ID_CHARACTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
private static final long CONNECTION_TIMEOUT = SECONDS.toMillis(30);
private static final long VALIDATION_TIMEOUT = SECONDS.toMillis(5);
private static final long IDLE_TIMEOUT = MINUTES.toMillis(10);
private static final long MAX_LIFETIME = MINUTES.toMillis(30);
/**
* Default constructor
*/
public HikariConfig()
{
dataSourceProperties = new Properties();
healthCheckProperties = new Properties();
minIdle = -1;
maxPoolSize = -1;
maxLifetime = MAX_LIFETIME;
//默认值30秒
connectionTimeout = CONNECTION_TIMEOUT;
validationTimeout = VALIDATION_TIMEOUT;
idleTimeout = IDLE_TIMEOUT;
initializationFailTimeout = 1;
isAutoCommit = true;
String systemProp = System.getProperty("hikaricp.configurationFile");
if (systemProp != null) {
loadProperties(systemProp);
}
}
}
2) connectTimeout
connectTimeout
参数因为是在com.mysql.cj.conf.PropertyKey
com.mysql.cj.conf.PropertyDefinitions
类中定义的,它的默认值是0,即表示可以无限时长地连接等待;所以它需要在配置文件jdbc连接属性spring.datasource.url
上设值,如jdbc:mysql://localhost:3306/{xx_db}?connectTimeout={numTime}
3. socketTimeout
socketTimeout是socket超时时间,即读超时。它在om.mysql.cj.conf.PropertyKey
com.mysql.cj.conf.PropertyDefinitions`类中定义,默认值是0,它也是在jdbc连接url上配置。
的socketTimeout在com.mysql.cj.protocol.a.NativeSocketConnection#connect
方法中真正得以应用,本质上就是为Socket为SO_TIMEOUT选项设值,tcp/ip协议底层对SO_TIMEOUT提供了支持,这跟应用层mysql协议无关。而queryTimeout
netTimeoutForStreamingResults
参数都是应用层mysql协议对它的支撑。
4 queryTimeout
queryTimeout
: sql执行超时。jdbc规范的Statement定义了这个超时时间(见java.sql.Statement#setQueryTimeout接口方法)。
如果使用原生的jdbc,则需要手动调用ava.sql.Statement#setQueryTimeout
设置sql执行超时。
国内实际上一般都使用mybatis这个orm框架,我们可以在配置文件中用mybatis.configuration.default-statement-timeout
配置全局默认的queryTimeout,当然也可以在指定的Mapper方法中单独配置queryTimeout(优先级比mybatis.configuration.default-statement-timeout高) 。
mybatis框架 BaseStatementHandler#prepare
中调用setStatementTimeout设值sql超时时间。
其逻辑是先取当前指定Statement的queryTimeout,若没有则取全局默认的queryTimemout。然后把此值跟spring事务注解@Transactional
配置的事务超时时间进行比较,最终的queryTimeout取两者中较小的那个值。
//StatementUtil
public static void applyTransactionTimeout(Statement statement, Integer queryTimeout, Integer transactionTimeout) throws SQLException {
if (transactionTimeout == null) {
return;
}
if (queryTimeout == null || queryTimeout == 0 || transactionTimeout < queryTimeout) {
statement.setQueryTimeout(transactionTimeout);
}
}
接下来来看看queryTimeout的实现原理,ClientPreparedStatement#executeInternal
方法在执行sql之前会调用startQueryTimer
尝试获取一个CancelQueryTask
超时任务 ,在执行完sql后尝试取消这个超时任务的,如果在超时前完成了sql查询,这时任务就被成功取消了,超时任务不会被执行。
startQueryTimer方法中的timeout参数是sql执行超时时间,PropertyKey.enableQueryTimeouts
属性默认值是true。因此只要sql执行超时不为空,就会创建一个CancelQueryTaskImpl任务,并且这个任务会在到达sql执行超时的时间线被执行(session.getCancelTimer().schedule(timeoutTask, timeout)延迟调度任务)。
我们再往下看看这个CancelQueryTaskImpl
任务是如何运行的。从下面的代码可以看出,CancelQueryTaskImpl.run
方法首先启动了一个线程,然后在这个线程中执行sql脚本KILL QUERY {query_threadId}
去杀掉这个查询线程。***注意:***这里是每次sql执行都会启动一个新线程,没有使用线程池(应该是为了保证超时任务能得到及时的调度,线程池中的线程数是有限的,任务数过多就会放在任务队列中,任务调度不可避免有一定延迟),在高并发的情况下会创建大量的线程,可能导致系统资源占用过高,甚至导致jvm虚拟机崩溃退出,所以在高并发环境中不建议使用sql执行超时这个功能。
5. transactionTimeout 和 innodb_lock_wait_timeout
transactionTimeout
:spring事务注解@Transactional
的超时时间,上面说到了,这个值将会作为sql执行超时,可以说它是客户端的事务超时参数。
innodb_lock_wait_timeout
: mysql server的环境变量,用于设置事务在等待获取锁时的超时时间。当一个事务请求锁资源时,如果该资源已经被其他事务锁定,那么该事务就会进入等待状态。如果一个事务等待获取锁的时间超过了该设置的时间,MySQL 将会自动中断该事务。因此这个参数可以说它是服务端的事务超时参数。
6. netTimeoutForStreamingResults
netTimeoutForStreamingResults
:主要用来在处理流式结果集时mysql server返回大量数据的超时时间,防止等待结果集的时间过长。
setupStreamingTimeout 根据流结果超时时间(PropertyKey.netTimeoutForStreamingResults的默认值是600)和是否需要流结果集方法createStreamingResultSet
来综合判断是否需要向服务端发送net_write_timeout
属性。
protected boolean createStreamingResultSet() {
return ((this.query.getResultType() == Type.FORWARD_ONLY) && (this.resultSetConcurrency == java.sql.ResultSet.CONCUR_READ_ONLY)
//getResultFetchSize默认值是0
&& (this.query.getResultFetchSize() == Integer.MIN_VALUE));
}