追溯 MySQL Statement Cancellation Timer

Memento發表於2021-03-05

原文

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問題

相關文章