読者です 読者をやめる 読者になる 読者になる

JDBCのStatement#cancelは即座の中断を期待してはいけないみたい

Java

今日ちょっと気付いたことなのでメモ。

JDBCの処理を行っていて、別スレッドから中断を行う仕組みを実装しようと思い、JDBCStatement#cancel() を使ってキャンセルする処理を実装してみました。
ところが、MySQLで試したところ、ロック待機を行っている UPDATE 文を実行している Statement に対して cancel() を実行したところ、ちっとも中断されず、ロックタイムアウトが起きるまでそのままでした。

何でだろうと思って調べてみたら、MySQLのバグトラッカにこんなチケットが上がっているのを見つけました。

JDBC statement.cancel() needs to actually cancel the current query
http://bugs.mysql.com/bug.php?id=36332

このチケットは "Not a Bug" として閉じられています。コメントの中に次のような記述がありました。

The issue as I see it is that the JDBC specification requires that cancel() (or statement timeout expiring) leaves the connection in a useful state. Therefore, the JDBC driver needs to use a mechanism that doesn't clobber the *network* side of the connection.

Therefore, we use "KILL QUERY" (on MySQL-5.0), which does have the interesting properties that it doesn't always return immediately, and sometimes one can wait a *long* time for the cancel, as SELECTs only check if they're killed in very specific places.

JDBCの仕様では、Statement#cancel() は実行後にコネクションがちゃんと使える状態で復帰するように実装することを求めているとのことです。
で、MySQLでは KILL QUERY を使ってこの処理を実装していますが、これはMySQLのマニュアルの記述によると、UPDATE や DELETE 操作の場合、各ブロックの読み込みや、行の各更新や削除の後でキルフラグを確認するため、場合によってはずっと待たされてしまうようです。

JDBCが安全な復帰を求めている以上、Statement#cancel() で無茶な中断はできないでしょうから、このように即座の中断を求めることは難しそうです。(先のMySQLのバグトラッカのコメントにも「JDBC EGに強制キャンセルの仕様をリクエストするしかないね」とありました)

強引な強制中断をしたければタイムアウトを併用するか、コネクションごと切っちゃうしかなさそうですね。