追溯 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.TimerThreadTimer.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 方法中可以發現, 當啟用 queryTimeouttimeoutInMillis!=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 查詢超時了, 則會在 timeoutTaskrun 方法里調用 com.mysql.jdbc.ConnectionImpl#realClose 來釋放 CancelTimer;
如果 Connection 正常關閉 close 時, 也會調用 com.mysql.jdbc.ConnectionImpl#realClose 來釋放 CancelTimer;

9. 閱讀資料

  1. 一次資料庫連接池優化的實踐剖析
  2. MySQL Statement Cancellation Timer問題