1. 前言
connectTimeout
: 連接超時
loginTimeout
: 登錄超時
socketTimeout
: Socket網絡超時,即讀超時
queryTimeout
: sql執行超時
transactionTimeout
:spring事務超時
innodb_lock_wait_timeout
:innodb鎖等待超時
wait_timeout
:非交互式連接關閉前的等待時間
interactive_wait_timeout
:交互式連接關閉前的等待時間
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@Overridepublic 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#connectionTimeoutthis.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執行超時,可以說它是客戶端的事務超時參數,mysql本身是不支持事務超時的,mysql只有請求鎖超時概念,這個是spring框架實現的事務超時。
innodb_lock_wait_timeout
: mysql server的環境變量,用于設置事務在等待獲取鎖時的超時時間。當一個事務請求鎖資源時,如果該資源已經被其他事務鎖定,那么該事務就會進入等待狀態。如果一個事務等待獲取鎖的時間超過了該設置的時間,MySQL 將會自動中斷該事務。
6. wait_timeout 和interactive_wait_timeout
wait_timeout
: 數據庫服務端非交互式連接關閉前的等待時間。非交互鏈接是指JDBC等編程工具建立的數據庫連接。
interactive_wait_timeout
: 數據庫服務端交互式連接關閉前的等待時間。交互式連接是指各種mysql UI客戶端建立的連接。
這兩個參數都是mysql server的環境變量,可以通過sql腳本set [GLOBAL] VARIABLES wait_timeout={timeNum};
設置。
mysql的默認全局wait_timeout是86400秒,大致8小時。這個參數過大,可能導致mysql服務端一直有Sleep的空閑線程,連接得不到釋放。當然過它過小也會導致在執行sql腳本時數據庫連接被莫名的關閉,發生’MySQL server has gone away’這種異常。
HikariCP連接池有一個maxLifetime
,這個參數表示一個連接的最大存活時間,達到這個閾值就JDBC客戶端就主動關閉這個連接。這里就避免了mysql客戶端wait_timeout
有大量的空閑線程.
7. 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));}