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)
- 下一篇: 瀚高DB兼容MySQL if函数