原文
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
;
9. 阅读资料
- 一次数据库连接池优化的实践剖析
- MySQL Statement Cancellation Timer问题