[mysql driver]query timeout实现 mysqldriverquery timeout
程序员文章站
2022-05-08 18:41:14
...
之前一直对jdbc的query timeout有疑惑,看了下mysql的实现,大概了解了。
关于jdbc的各种超时时间关系见这个文章http://www.importnew.com/2466.html,这里简单来看一下mysql的query timeout实现,也是比较简单。query timeout就是在发起请求前,启动一个定时任务,触发之后抛出异常。
核心代码再执行sql过程中
protected synchronized ResultSetInternalMethods executeInternal(int maxRowsToRetrieve, Buffer sendPacket, boolean createStreamingResultSet, boolean queryIsSelectOnly, Field[] metadataFromCache, boolean isBatch) throws SQLException { try { //恢复超时状态 resetCancelledState(); //物理连接 MySQLConnection locallyScopedConnection = this.connection; this.numberOfExecutions++; if (this.doPingInstead) { doPingInstead(); return this.results; } ResultSetInternalMethods rs; CancelTask timeoutTask = null; try { //如果设置了query timeout,则启动一个timer任务 if (locallyScopedConnection.getEnableQueryTimeouts() && this.timeoutInMillis != 0 && locallyScopedConnection.versionMeetsMinimum(5, 0, 0)) { timeoutTask = new CancelTask(this); locallyScopedConnection.getCancelTimer().schedule(timeoutTask, this.timeoutInMillis); } //这里执行sql,因为mysql是bio的,所以正常的话线程会等待server端返回,要么就socket read timeout抛出异常 rs = locallyScopedConnection.execSQL(this, null, maxRowsToRetrieve, sendPacket, this.resultSetType, this.resultSetConcurrency, createStreamingResultSet, this.currentCatalog, metadataFromCache, isBatch); //如果需要超时,即使服务端正常返回了,还是会抛出超时异常 if (timeoutTask != null) { //先把timer任务停了,大部分情况下超时任务还没到点触发 timeoutTask.cancel(); locallyScopedConnection.getCancelTimer().purge(); //如果在进行超时操作的时候抛出了异常,则直接抛出这个异常 if (timeoutTask.caughtWhileCancelling != null) { throw timeoutTask.caughtWhileCancelling; } timeoutTask = null; } synchronized (this.cancelTimeoutMutex) { if (this.wasCancelled) { SQLException cause = null; //如果被超时处理了,则抛出MySQLTimeoutException if (this.wasCancelledByTimeout) { cause = new MySQLTimeoutException(); } //如果是被cancel了,就抛出cancel异常 else { cause = new MySQLStatementCancelledException(); } //完了之后,重新恢复状态,等待下一次sql请求 resetCancelledState(); //抛出异常 throw cause; } } } finally { //最后的时候,取消超时任务,大部分场景都是正常访问,timer没到点就返回了 if (timeoutTask != null) { timeoutTask.cancel(); locallyScopedConnection.getCancelTimer().purge(); } } return rs; } catch (NullPointerException npe) { checkClosed(); // we can't synchronize ourselves against async connection-close // due to deadlock issues, so this is the next best thing for // this particular corner case. throw npe; } }
具体的超时实现CancelTask
public void run() { //超时的时候,杀连接 if (connection.getQueryTimeoutKillsConnection()) { try { toCancel.wasCancelled = true; toCancel.wasCancelledByTimeout = true; connection.realClose(false, false, true, new MySQLStatementCancelledException(Messages.getString("Statement.ConnectionKilledDueToTimeout"))); } catch (NullPointerException npe) { // not worth guarding against } catch (SQLException sqlEx) { caughtWhileCancelling = sqlEx; } } else { //正常的关闭当前sql请求 Connection cancelConn = null; java.sql.Statement cancelStmt = null; try { synchronized (cancelTimeoutMutex) { //复制一个连接,这里会重新创建新的连接,如果此时db down了,则会connect timeout。然后发送kill指令,如果db down了,发送不成功,抛出异常,此时cancel失败 if (origConnURL == connection.getURL()) { //All's fine cancelConn = 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; } } //如果超时任务抛出异常,比如db挂了,则给主线程一个异常 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; } } }
从上可知,如果发送sql请求之后db挂了,query timeout触发时还没恢复,则query timeout不成功,此时主线程还会等待server端返回直到socket read timeout抛出异常,而如果socket read timeout设置比query timeout小,则query timeout始终无效,因为超时任务还没启动,主线程就抛出socket timeout异常了。
上一篇: MySQL存储过程使用游标删除多表数据
下一篇: 教你只需三分钟,增强身体柔韧性