jdbc cancel mysql_当执行Statement的cancel()之后发生了什么?
java.sql.Statement接口中有cancel()方法;
/*** Cancels this Statement object if both the DBMS and* driver support aborting an SQL statement.* This method can be used by one thread to cancel a statement that* is being executed by another thread.** @exception SQLException if a database access error occurs or* this method is called on a closed Statement* @exception SQLFeatureNotSupportedException if the JDBC driver does not support* this method*/
void cancel() throws SQLException;
首先:當我們的Statement執行executeQuery(String sql)的時候,其實執行該邏輯的線程會被阻塞一段時間從而等待網絡IO返回或者超時;
所以你看注釋上說
This method can be used by one thread to cancel a statement that is being executed by another thread.
例如在com.mysql.jdbc.StatementImpl類中的cancel實現,是向數據庫發送了Kill Query指令
/*** Cancels this Statement object if both the DBMS and driver support* aborting an SQL statement. This method can be used by one thread to* cancel a statement that is being executed by another thread.*/
public void cancel() throws SQLException {
if (!this.statementExecuting.get()) {
return;
}
if (!this.isClosed &&
this.connection != null &&
this.connection.versionMeetsMinimum(5, 0, 0)) {
Connection cancelConn = null;
java.sql.Statement cancelStmt = null;
try {
cancelConn = this.connection.duplicate();
cancelStmt = cancelConn.createStatement();
cancelStmt.execute("KILL QUERY "
+ this.connection.getIO().getThreadId());
this.wasCancelled = true;
} finally {
if (cancelStmt != null) {
cancelStmt.close();
}
if (cancelConn != null) {
cancelConn.close();
}
}
}
}
首先我們看到在com.mysql.jdbc.StatementImpl類當中有一個內部類
/*** Thread used to implement query timeouts...Eventually we could be more* efficient and have one thread with timers, but this is a straightforward* and simple way to implement a feature that isn't used all that often.*/
class CancelTask extends TimerTask {
long connectionId = 0;
String origHost = "";
SQLException caughtWhileCancelling = null;
StatementImpl toCancel;
Properties origConnProps = null;
String origConnURL = "";
CancelTask(StatementImpl cancellee) throws SQLException
{
connectionId = cancellee.connectionId;
origHost = connection.getHost();
toCancel = cancellee;
origConnProps = new Properties();
Properties props = connection.getProperties();
Enumeration> keys = props.propertyNames();
while (keys.hasMoreElements())
{
String key = keys.nextElement().toString();
origConnProps.setProperty(key, props.getProperty(key));
}
origConnURL = connection.getURL();
}
public void run() {
Thread cancelThread = new Thread() {
public void run() {
Connection cancelConn = null;
java.sql.Statement cancelStmt = null;
try {
if (connection.getQueryTimeoutKillsConnection())
{
toCancel.wasCancelled = true;
toCancel.wasCancelledByTimeout = true;
connection.realClose(false, false, true,
new MySQLStatementCancelledException(
Messages.getString("Statement.ConnectionKilledDueToTimeout")));
} else {
synchronized (cancelTimeoutMutex) {
if (origConnURL.equals(connection.getURL())) {
//All's finecancelConn = connection.duplicate();
cancelStmt = cancelConn.createStatement();
cancelStmt.execute("KILL QUERY " + connectionId);
} else {
try {
cancelConn = (Connection) DriverManager.getConnection(origConnURL, origConnProps);
cancelStmt = cancelConn.createStatement();
cancelStmt.execute("KILL QUERY " + connectionId);
} catch (NullPointerException npe){
//Log this? "Failed to connect to " + origConnURL + " and KILL query"}
}
toCancel.wasCancelled = true;
toCancel.wasCancelledByTimeout = true;
}
}
} catch (SQLException sqlEx) {
caughtWhileCancelling = sqlEx;
} catch (NullPointerException npe) {
// Case when connection closed while starting to cancel// We can't easily synchronize this, because then one thread// can't cancel() a running query
// ignore, we shouldn't re-throw this, because the connection's// already closed, so the statement has been timed out.} finally {
if (cancelStmt != null) {
try {
cancelStmt.close();
} catch (SQLException sqlEx) {
throw new RuntimeException(sqlEx.toString());
}
}
if (cancelConn != null) {
try {
cancelConn.close();
} catch (SQLException sqlEx) {
throw new RuntimeException(sqlEx.toString());
}
}
toCancel = null;
origConnProps = null;
origConnURL = null;
}
}
};
cancelThread.start();
}
}
該類是通用的超時定時任務
在所有的sql操作中都有該定時器任務的身影
比如在executeQuery(String sql)中
if (locallyScopedConn.getEnableQueryTimeouts()
&&this.timeoutInMillis != 0
&&locallyScopedConn.versionMeetsMinimum(5, 0, 0))
{
timeoutTask = new CancelTask(this);
locallyScopedConn.getCancelTimer().schedule(
timeoutTask,this.timeoutInMillis);
}
在查詢請求真正發送之前就啟動了該定時任務;
在超時時間內若沒有得到響應,則該任務將會得到執行,觸發超時異常,并且中斷卡在IO處的statement線程;
------------------
其中StatementImpl類中有以下幾個變量也是值得關注的
/** Mutex to prevent race between returning query results and noticingthat we're timed-out or cancelled. */
protected Object cancelTimeoutMutex = new Object();
protected boolean wasCancelled = false;
protected boolean wasCancelledByTimeout = false;
/** Are we currently executing a statement? */
protected final AtomicBoolean statementExecuting = new AtomicBoolean(false);
特別是這個mutex變量,用于控制設置cancel以及結果返回后判斷是否已經被cancel的并發操作;
比如在executeQuery中
synchronized (this.cancelTimeoutMutex)
{
if (this.wasCancelled)
{
SQLException cause = null;
if (this.wasCancelledByTimeout)
{
cause = new MySQLTimeoutException();
}
else
{
cause = new MySQLStatementCancelledException();
}
resetCancelledState();
throw cause;
}
}
當結果返回后會進行判斷,是否是已經被取消了
并且在其后的finally方法中設置了正在執行為false,從而避免了外部的cancel方法繼續執行;
finally {
this.statementExecuting.set(false);
if (timeoutTask != null) {
timeoutTask.cancel();
locallyScopedConn.getCancelTimer().purge();
}
if (oldCatalog != null) {
locallyScopedConn.setCatalog(oldCatalog);
}
}
但是整個cancel方法以及executeQuery方法并沒有完全阻止并發,一旦出現結果已經查詢完畢,且cancel也設置成功之后,將會出現什么情況?
其實不用擔心,因為結果已經查詢出來了,程序仍舊可以繼續執行,只不過IO被強行關閉了;
總結
以上是生活随笔為你收集整理的jdbc cancel mysql_当执行Statement的cancel()之后发生了什么?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据挖掘概念与技术——读书笔记(1)
- 下一篇: python判断图像是否为灰度图