假设我们想对某一条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)) {
timeoutTask = new CancelTask(this);
locallyScopedConn.getCancelTimer().schedule(timeoutTask, this.timeoutInMillis);
}
if (!locallyScopedConn.getCatalog().equals(this.currentCatalog)) {
oldCatalog = locallyScopedConn.getCatalog();
locallyScopedConn.setCatalog(this.currentCatalog);
}
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 , 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())) {
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) {
}
}
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
命令,那么自然就不生效了
<script type="text/javascript">
$(function () {
$('pre.prettyprint code').each(function () {
var lines = $(this).text().split('\n').length;
var $numbering = $('<ul/>').addClass('pre-numbering').hide();
$(this).addClass('has-numbering').parent().append($numbering);
for (i = 1; i <= lines; i++) {
$numbering.append($('<li/>').text(i));
};
$numbering.fadeIn(1700);
});
});
</script>
分享到:
相关推荐
达梦数据库的连接配置 达梦数据库的连接配置是指在 Java 应用程序中连接达梦数据库的过程。该过程主要包括两个方面:JDBC 连接和 iBatis 连接设置。 JDBC 连接 JDBC(Java Database Connectivity)是 Java 应用...
可实现基于XML文件的异构数据交互的功能。 本软件基于工作需要而开发,...原始set.xml数据库连接节点缺少以上两个节点请添加,LoginTimeOut为数据库连接超时时间,单位秒,QueryTimeOut为SQL语句执行超时时间单位秒。
Dim queryTimeout As String = ConfigurationManager.AppSettings("QueryTimeout") Return Integer.Parse(queryTimeout) End Function Public Shared Sub UpdateQueryTimeout(newTimeout As Integer) Dim ...
Spark SQL 是 Spark 的组件之一,它扩展了 Spark 对结构化数据的支持,允许用户通过 SQL 或者DataFrame API 来处理数据。Spark SQL 提供了一个统一的接口来处理结构化数据,无论数据源是 Hadoop 文件系统、Hive 表,...
它遍历父节点的子节点,如果所有给定的键值对都与节点的属性匹配,则返回该节点对应的`SimpleXmlGetter`对象。 ```python def __call__(self, *args, **kwargs): for e in self.root.parentNode.childNodes: if e...
- 执行Access标准宏不支持的操作,比如事务处理(提交COMMIT和回滚ROLLBACK)。 - 实现动态数据交换(DDE)操作。 - 同时处理多个数据库。 - 文档化的应用程序开发。 - 创建自定义用户函数。 - 显示详细的错误...
KRPC.js 编码消息的简单 KRPC 协议实现。 有关更多详细信息,请参阅。 安装 $ npm install krpc 应用程序接口 krpc.on('解析错误') krpc.on('查询') krpc.on('query_{type}') ... queryTimeout :
- **数据库游标访问函数**:如`attr`, `close`, `cols`, `columnnames`, `fetch`, `get`, `querytimeout`, `rows`, `set`, `width`等。 - **数据库元数据函数**:如`bestrowid`, `columnprivileges`, `columns`, `...
- **QueryTimeOut**:查询超时时间,默认1800秒。 3. **不同版本对比**: - 在SQL Server 2008 R2和2016 SP1中,快照代理在处理大量记录时表现出差异。2016 SP1采用了更均匀的数据分块策略,将大文件拆分为多个较...