queryTimeout对Cobar不生效的原因
来源:互联网 发布:双节棍 知乎 编辑:程序博客网 时间:2024/05/21 06:37
假设我们想对某一条sql做超时限制,我们可能会采用如下的方式:
public static void main(String[] args) { ApplicationContext ctx = SpringApplication.run(SpringBootDemoApplication.class, args); JdbcTemplate jdbcTemplate = ctx.getBean(JdbcTemplate.class); jdbcTemplate.setQueryTimeout(1); jdbcTemplate.execute("select * from `order` where id = 49320135 for update"); }
这种方式在连接普通mysql
实例的时候是没有问题的。但是在连接cobar
时配置queryTimeout
是不生效的,这是为什么呢?
刚碰到这个问题时我是很迷惑的。因为一般来讲超时本身应该是在客户端做控制的,和服务端(mysql
实例或者cobar
实例)是没有关系的。但是现象确实存在,那么就只能调试源码了,也许设计者和我的想法不太一样呢
直接从JdbcTemplate.execute
出发:
public void execute(final String sql) throws DataAccessException { if (logger.isDebugEnabled()) { logger.debug("Executing SQL statement [" + sql + "]"); } class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider { @Override public Object doInStatement(Statement stmt) throws SQLException { stmt.execute(sql); return null; } @Override public String getSql() { return sql; } } execute(new ExecuteStatementCallback()); }
其中doInStatement(Statement stmt)
方法中的参数Statement
的实现类是StatementImpl
,我们重点看下这个类,因为queryTimeout
实际上最终就是设置到Statement
层面的,我们看看其中executeQuery
方法:
public java.sql.ResultSet executeQuery(String sql) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { // 省略部分代码... CancelTask timeoutTask = null; String oldCatalog = null; try { if (locallyScopedConn.getEnableQueryTimeouts() && this.timeoutInMillis != 0 && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) { // 这里用到了timeout参数,并启动了一个timeoutTask,等下会重点看下 timeoutTask = new CancelTask(this); locallyScopedConn.getCancelTimer().schedule(timeoutTask, this.timeoutInMillis); } if (!locallyScopedConn.getCatalog().equals(this.currentCatalog)) { oldCatalog = locallyScopedConn.getCatalog(); locallyScopedConn.setCatalog(this.currentCatalog); } // // Check if we have cached metadata for this query... // Field[] cachedFields = null; if (locallyScopedConn.getCacheResultSetMetadata()) { cachedMetaData = locallyScopedConn.getCachedMetaData(sql); if (cachedMetaData != null) { cachedFields = cachedMetaData.fields; } } locallyScopedConn.setSessionMaxRows(this.maxRows); statementBegins(); this.results = locallyScopedConn.execSQL(this, sql, this.maxRows, null, this.resultSetType, this.resultSetConcurrency, doStreaming, this.currentCatalog, cachedFields); if (timeoutTask != null) { if (timeoutTask.caughtWhileCancelling != null) { throw timeoutTask.caughtWhileCancelling; } timeoutTask.cancel(); locallyScopedConn.getCancelTimer().purge(); timeoutTask = null; } synchronized (this.cancelTimeoutMutex) { if (this.wasCancelled) { SQLException cause = null; if (this.wasCancelledByTimeout) { cause = new MySQLTimeoutException(); } else { cause = new MySQLStatementCancelledException(); } resetCancelledState(); throw cause; } } } finally { this.statementExecuting.set(false); if (timeoutTask != null) { timeoutTask.cancel(); locallyScopedConn.getCancelTimer().purge(); } if (oldCatalog != null) { locallyScopedConn.setCatalog(oldCatalog); } } this.lastInsertId = this.results.getUpdateID(); if (cachedMetaData != null) { locallyScopedConn.initializeResultsMetadataFromCache(sql, cachedMetaData, this.results); } else { if (this.connection.getCacheResultSetMetadata()) { locallyScopedConn.initializeResultsMetadataFromCache(sql, null /* will be created */, this.results); } } return this.results; } }
可以看到如果queryTimeout
大于0,那么就会启动一个CancelTask
:
public void run() { Thread cancelThread = new Thread() { @Override public void run() { Connection cancelConn = null; java.sql.Statement cancelStmt = null; try { if (StatementImpl.this.connection.getQueryTimeoutKillsConnection()) { CancelTask.this.toCancel.wasCancelled = true; CancelTask.this.toCancel.wasCancelledByTimeout = true; StatementImpl.this.connection.realClose(false, false, true, new MySQLStatementCancelledException(Messages.getString("Statement.ConnectionKilledDueToTimeout"))); } else { synchronized (StatementImpl.this.cancelTimeoutMutex) { if (CancelTask.this.origConnURL.equals(StatementImpl.this.connection.getURL())) { //All's fine cancelConn = StatementImpl.this.connection.duplicate(); cancelStmt = cancelConn.createStatement(); cancelStmt.execute("KILL QUERY " + CancelTask.this.connectionId); } else { try { cancelConn = (Connection) DriverManager.getConnection(CancelTask.this.origConnURL, CancelTask.this.origConnProps); cancelStmt = cancelConn.createStatement(); cancelStmt.execute("KILL QUERY " + CancelTask.this.connectionId); } catch (NullPointerException npe) { //Log this? "Failed to connect to " + origConnURL + " and KILL query" } } CancelTask.this.toCancel.wasCancelled = true; CancelTask.this.toCancel.wasCancelledByTimeout = true; } } } catch (SQLException sqlEx) { CancelTask.this.caughtWhileCancelling = sqlEx; } catch (NullPointerException npe) { } 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()); } } CancelTask.this.toCancel = null; CancelTask.this.origConnProps = null; CancelTask.this.origConnURL = null; } } }; cancelThread.start(); }
可以看到CancelTask
大致分为两步。第一步是通过Kill Query threadId
命令先将数据库的查询线程杀死。然后第二步再将该查询标记为取消。并且通过第一步将该查询kill
掉之后,那么本身阻塞的查询会立即返回,并抛出com.mysql.jdbc.exceptions.jdbc4.MySQLQueryInterruptedException
再来看看MysqlIO
中sqlQueryDirect
方法catch
里的逻辑处理:
if (this.statementInterceptors != null) { invokeStatementInterceptorsPost(query, callingStatement, null, false, sqlEx); } if (callingStatement != null) { synchronized (callingStatement.cancelTimeoutMutex) { if (callingStatement.wasCancelled) { SQLException cause = null; if (callingStatement.wasCancelledByTimeout) { cause = new MySQLTimeoutException(); } else { cause = new MySQLStatementCancelledException(); } callingStatement.resetCancelledState(); throw cause; } } } throw sqlEx;
可以看到对于canceledByTimeout
这种情况的,mysql-connector
会重新抛出一个MySQLTimeoutException
的新异常。那么实际上我们应用只需要根据此异常来判断是否为执行超时就可以了。
总结:sql的queryTimeout
机制是通过kill query threadId
命令来实现的,而cobar不支持kill query
命令,那么自然就不生效了
- queryTimeout对Cobar不生效的原因
- 【Linux】limits.conf 不重启就生效或者不生效的原因
- python logging.basicConfig不生效的原因
- js事件不生效的原因
- 对JMenuItem的鼠标监听器不生效
- git ignore不生效原因
- CSS的z-index设置不生效的原因
- neutron的安全组规则不生效的原因
- neutron的安全组规则不生效的原因
- 悬浮窗口的显示位置设置不生效的原因
- tomcat-users.xml配置不生效的原因
- AJAX请求php写COOKIE不生效的原因
- linux Apache rotatelogs 失败不生效的原因和解决办法
- 关于修改log4j配置不生效的原因
- Android Studio Git .gitignore规则不生效的原因
- swift添加导航条按钮,不生效的原因
- ss在windows下不生效的原因
- linux Apache rotatelogs 失败不生效的原因和解决办法
- [2016ICPC 青岛网络预选赛] HDU 5889 网络流
- Console命令的常见应用
- UVa - 489 - Hangman Judge(刽子手游戏)
- fastjson是阿里巴巴出的号称最快解析速度的插件
- iOS实现一个颜色渐变的弧形进度条
- queryTimeout对Cobar不生效的原因
- 接口签名认证思路
- html 块,布局
- struts2中使用ajax
- Can't find messagefile '/usr/local/mysql/share/errmsg.sys'
- oracle存储过程中is和as区别
- js 作用域
- Android IntentService解析
- Java多线程之ForkJoinTask,ForkJoinPool介绍及使用