追溯 MySQL Statement Cancellation Timer
1. 背景
在 jstack
的內容中可以看到以下的 MySQL Statement Cancellation Timer
守護執行緒, 在業務高峰期的時候會出現大量的這類守護執行緒, 由此追溯該執行緒的生命周期過程;
"MySQL Statement Cancellation Timer" #20647 daemon prio=5 os_prio=0 tid=0x00007f2d087e9800 nid=0xfb83 in Object.wait() [0x00007f2b4b45a000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.util.TimerThread.mainLoop(Timer.java:552)
- locked <0x00000005da147038> (a java.util.TaskQueue)
at java.util.TimerThread.run(Timer.java:505)
Locked ownable synchronizers:
- None
"MySQL Statement Cancellation Timer" #24138 daemon prio=5 os_prio=0 tid=0x00007f402802c800 nid=0x4cf64 in Object.wait() [0x00007f3e49453000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at java.util.TimerThread.mainLoop(Timer.java:526)
- locked <0x00000005f606cc60> (a java.util.TaskQueue)
at java.util.TimerThread.run(Timer.java:505)
Locked ownable synchronizers:
- None
2. TimerThread
java.util.TimerThread
是 Timer.java
文件里的一個內部類, 主要負責 Timer
隊列任務的執行和調度;
- 根據定位
Timer.java:526
位置的程式碼, 當前狀態WAITING (on object monitor)
, 表示當前的timer
執行緒池為空, 正在等待新入駐; - 根據定位
Timer.java:552
位置的程式碼, 當前狀態TIMED_WAITING (on object monitor)
表示任務等待被激活;
3. getCancelTimer
根據執行緒名稱 MySQL Statement Cancellation Timer
繼續追溯, 在 com.mysql.jdbc.ConnectionImpl#getCancelTimer
方法中找到該 TimerThread
的創建(cancelTimer
):
4. getCancelTimer 的上游調用
主要是 mysql-connector-java-xxx.jar
中負責 sql 查詢的 Statement
5. 創建 CancelTask timeoutTask
在 com.mysql.jdbc.StatementImpl#executeQuery
方法中可以發現, 當啟用 queryTimeout
且 timeoutInMillis!=0
時, 在執行 sql 的時候就會創建一個 CancelTask
的執行緒來控制超時; (後面那個 versionMeetsMinimum
是個版本判斷可以先忽略)
然後在項目的 application.yml
中發現配置 mybatis.configuration.default-statement-timeout: 5
, 所以 mybatis
在每次的資料庫查詢都會加上 queryTimeout
, 且該配置對全局 SQL 生效, 包括 insert
, select
, update
;
6. CancelTask 執行過程
在 com.mysql.jdbc.StatementImpl.CancelTask#run
方法中, 會另起一個執行緒, 判斷如果啟用了 queryTimeoutKillsConnection
的配置時, 會調用當前 Statement
對應的 Connection
里的 realClose
方法;
在 realClose
方法里發現會關閉 cancelTimer
執行緒;
7. Connection 關閉時
在 com.mysql.jdbc.ConnectionImpl#close
方法里也會發現有 realClose
方法的調用, 即在連接關閉時也會處理 cancelTimer
的釋放
8. 總結 MySQL Statement Cancellation Timer 執行緒的流程
設置了 queryTimeout
會使 jdbc driver
在每次查詢資料庫時新建 CancelTask
(timeoutTask
對象) 執行緒來處理超時, 並使用 CancelTimer
(在 ConnectionImpl
類中) 來進行調度;
如果 SQL
查詢超時了, 則會在 timeoutTask
的 run
方法里調用 com.mysql.jdbc.ConnectionImpl#realClose
來釋放 CancelTimer
;
如果 Connection
正常關閉 close
時, 也會調用 com.mysql.jdbc.ConnectionImpl#realClose
來釋放 CancelTimer
;