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
;