环境 : jdk1.8
架构 : Spring Boot 1.5.7
+ Mybatis 3.4.3
+ Druid
+ PostgreSQL JDBC Driver
我在使用 PostgreSQL 执行一些查询时报了一个错误,花了半天时间用来搞懂这个错误产生的原因后,决定在这里总结一下.
ERROR: canceling statement due to user request
异常抛出原因.
报这个错误的主要原因是和字面上的意义一致,“由于用户的请求取消了当前查询的状态”.
Caused by: org.postgresql.util.PSQLException: ERROR: canceling statement due to user request
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2477)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2190)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:300)
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:428)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:354)
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:169)
at org.postgresql.jdbc.PgPreparedStatement.execute(PgPreparedStatement.java:158)
可能解释的有点草率,但这里也没有办法说明的更加详细了.大致意思就是由于你代码中设置了取消执行当前查询的代码从而导致抛出这个异常.
异常的原因有可能是执行了该函数java.sql.Statement
:
// java.sql.Statement:248-259
/**
* Cancels this <code>Statement</code> 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 <code>Statement</code>
* @exception SQLFeatureNotSupportedException if the JDBC driver does not support
* this method
*/
void cancel() throws SQLException;
在 PostgreSQL JDBC Driver
驱动中对该方法的实现代码是:
//org.postgresql.jdbc.PgStatement:595-606
public void close() throws SQLException {
// closing an already closed Statement is a no-op.
if (isClosed) {
return;
}
cleanupTimer();
closeForNextExecution();
isClosed = true;
}
我目前遇到的两种问题可能会导致爆出这个错误.
- 当用户发起 Http 请求,当该请求触发了 Sql 查询后,当还没有返回数据的时候,用户取消了该请求会导致抛出该异常;
- 当在 Mybatis 的配置文件
mybatis-config.xml
中设置了defaultStatementTimeout
属性(单位:秒)后当sql的查询时间超过了这个设置时间后会抛出该异常;
第一种情况不太确定.但第二种情况下驱动代码PostgreSQL JDBC Driver
首先会在查询前基于defaultStatementTimeout
设置的时间创建一个定时器.
//org.postgresql.jdbc:872-888
private void startTimer() {
/*
* there shouldn't be any previous timer active, but better safe than sorry.
*/
cleanupTimer();
STATE_UPDATER.set(this, StatementCancelState.IN_QUERY);
if (timeout == 0) {
return;
}
TimerTask cancelTask = new TimerTask() {
public void run() {
try {
if (!CANCEL_TIMER_UPDATER.compareAndSet(PgStatement.this, this, null)) {
// Nothing to do here, statement has already finished and cleared
// cancelTimerTask reference
return;
}
PgStatement.this.cancel(); //**定时器中取消本次查询的代码**
} catch (SQLException e) {
}
}
};
CANCEL_TIMER_UPDATER.set(this, cancelTask);
connection.addTimerTask(cancelTask, timeout);//创建定时器,并且设置超时时间
}
在执行查询时间超过定时器设置的时间后,定时器会启动并且去关闭本次sql执行.在执行完成后,等待查询的循环判断得到取消查询抛出了异常时,会将该异常包装并且抛出.